UWP程序用多页面实现应用实例多开

 2025-3-26 17:54:03     作者:杨阳     联系作者    

Windows 10 IoT ARM64平台下,UWP应用和MFC程序不一样,同时只能打开一个应用实例。以串口程序为例,如果用户希望同时打开多个应用实例,一个应用实例打开串口1,一个应用实例打开串口2,那么我们可以加载多个页面,在各个页面分别加载功能模块,实现程序功能模块多开。

本文以UWP串口例程为例,介绍如何用代码实现同时打开多个串口模块页面,进行测试。


 1、创建UWP工程 

1. 打开visual studio 2022,点击“创建新项目”。

2. 选择筛选条件,语言C++,平台选择Windows,应用类型选择UWP。

3.  选择“空白应用”,本文以C++/CX为例。



 2、导入需要多开的功能模块页面 

1.  创建工程后,右键点击工程->添加->新建项。

8.png


2. 选择XAML空白页,给页面命名为SerialPage.xaml,点击添加。

9.png


3.  在工程中可以看到添加的页面SerialPage,将之前写好的UWP串口程序代码COPY过来(参考文章《UWP串口程序开发》),把串口程序的xaml内容拷贝到SerialPage.xaml中,把代码文件.xaml.h和.xaml.cpp的内容也拷贝到对应文件SerialPage.xaml.h和SerialPage.xaml.cpp中。如果原UWP程序页面名称(默认为MainPage)和当前工程名称(SerialPage)不一样,注意全部替换一下。

10.png


4. 双击页面SerialPage.xaml,检查下是否添加成功。编译一下,确认代码都正确COPY过来了。

 11.png



3、通过Navigate加载新页面 

UWP中页面Frame的概念,类似MFC中的窗口Dialog,但功能更强大些。主页面可以打开新的子页面,页面本身也可以执行跳转,回退操作。页面的回收由系统负责,当没有Content指向页面后,页面自动被回收

在主页面中添加一个按钮,按钮中执行代码。

Windows::UI::Xaml::Controls::Frame^ frame= new ref new Windows::UI::Xaml::Controls::Frame();
frame->Navigate(TypeName(SerialPage::typeid));

设置新页面为当前操作页面。

Window.Current.Content = frame;
Window.Current.Activate();

每点击一次按钮,就可以加载一个新的SerialPage页面了。

 4、用导航控件SplitView管理多窗口 

为了便于管理打开的新页面,能在它们之间随意切换,可以使用SplitView控件来实现。SplitView由一个导航区域和一个页面区域组成,可以在导航区域Panel中添加不同按钮,在点击后,在页面区域Content中显示对应的页面。根据需求,可以设置导航区域大小,是否自动隐藏等。

1. 在主页面中添加一个SplitView控件,命名为SplitViewMain。在它的Pane中可以用一个StackPanel来装按钮,给Content命名为FrameMain,在里面随意加一段文字。

<SplitView x:Name="SplitViewMain" DisplayMode="Inline" IsPaneOpen="True" >
    <SplitView.Pane>
        <StackPanel x:Name="BtnContainer">
        </StackPanel>
    </SplitView.Pane>
    <SplitView.Content>
        <Frame x:Name="FrameMain">
            <TextBlock Text="未添加测试接口" VerticalAlignment="Center" HorizontalAlignment="Center"/>
        </Frame>
    </SplitView.Content>
</SplitView>


2. 在Pane中添加几个按钮,可以加到StackPanel中,让StackPanel来自动排列。

<SplitView.Pane>
    <StackPanel x:Name="BtnContainer">
        <Button Content="Button1" Click="Button1_Click"/>
        <Button Content="Button2" Click="Button2_Click"/>
    </StackPanel>
</SplitView.Pane>


3. 在按钮中加入代码

void SPT::MainPage:: Button1_Click (Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
       if (m_frame[0] == nullptr)
       {
              m_frame[0] = ref new Windows::UI::Xaml::Controls::Frame();
m_frame[0]->Navigate(TypeName(SerialPage::typeid));           //指定SerialPage
       }
       FrameMain = m_frame[0];
       SplitViewMain->Content = FrameMain;
}

这样,就可以通过点击不同按钮,进入对应的页面了

 5、动态管理导航控件SplitView内导航按钮 

有时候,如果导航按钮的数量不确定,希望根据实际情况添加或删除导航按钮。本文例程中,每点击一次添加按钮,便添加一个串口测试页面,做法如下。

1. 在主页面中添加一个按钮,点击一次,便在SplitView的Pane里加入一个按钮,给这个按钮设置参数Tag,便于区分不同按钮。给按钮绑定点击响应函数OnClick。

void SPT::MainPage::BtnSerial_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
       Button^ newButton;
       newButton = ref new Button;
m_Count++;
       newButton->Tag = m_Count;
       newButton->Click += ref new Windows::UI::Xaml::RoutedEventHandler(this, &SPT::MainPage::OnClick);
       BtnContainer->Children->Append(newButton);
       m_frame[m_Count] = ref new Windows::UI::Xaml::Controls::Frame();
       m_frame[m_Count]->Navigate(TypeName(SerialPage::typeid));
       FrameMain = m_frame[m_Count];
       SplitViewMain->Content = FrameMain;
}


2. 在按钮的响应函数中读出按钮参数,根据不同的参数决定如何处理,加载哪个页面。

void SPT::MainPage::OnClick(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
       Button^ clickButton = safe_cast<Button^>(sender);
       int id = safe_cast<int>(clickButton->Tag);
      
       if (m_frame[id] == nullptr)
       {
              return;
       }
       FrameMain = m_frame[id];
       SplitViewMain->Content = FrameMain;
}

 


6、主页面与导航打开的新页面之间数据通信 

目前导航页面和加载的子页面相对独立,很多时候主界面需要传递数据给子页面,便于子页面初始化,甚至需要给子页面提供函数接口。子页面退出时也需要回调函数通知主页面,并返回处理后的数据。

本文串口例程中

1. 设置一个数据结构,把所有主页面要传输的数据打包到这个数据接口中。文本例程中打包数据包括子页面ID,和一个回调接口。

public ref class PageData sealed : public Platform::Object
{
public:
       PageData(int initValue, Windows::Foundation::EventHandler<int>^ callback)
              : _initValue(initValue), _exitCallback(callback){}
       property int InitValue {int get() { return _initValue; }}
       property Windows::Foundation::EventHandler<int>^ ExitCallback {
              Windows::Foundation::EventHandler<int>^ get() { return _exitCallback; }
       }
private:
       int _initValue;
       Windows::Foundation::EventHandler<int>^ _exitCallback;
};


2. 在主页面加载子页面时,将打包数据一起传过去,修改Navigate调用。

m_frame[i] = ref new Windows::UI::Xaml::Controls::Frame();
auto exitHandler = ref new Windows::Foundation::EventHandler<int>(
       this, &MainPage::OnPageExit);
auto data = ref new PageData(i, exitHandler);
m_frame[i]->Navigate(TypeName(SerialPage::typeid), data);
FrameMain = m_frame[i];
SplitViewMain->Content = FrameMain;


3. 子页面SerialPage重载OnNavigatedTo函数,添加初始化代码

virtual void OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) override;
Windows::Foundation::EventHandler<int>^ m_ExitCallback;
 
void SerialPage::OnNavigatedTo(NavigationEventArgs^ e)
{
       auto data = dynamic_cast<PageData^>(e->Parameter);
       if (data != nullptr)
       {
              m_id = data->InitValue;
              m_ExitCallback = data->ExitCallback;
       }
}


4. 子页面退出时调用回调函数,传回参数页面ID

void SPT::SerialPage::BtnExit_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
       try
       {
              CancelReadTask();
              ClosePort();
       }
       catch (Platform::Exception^ ex)
       {
       }
       if (Frame->CanGoBack)
       {
              Frame->GoBack();
       }
       Frame->Content = nullptr;
       if (m_ExitCallback != nullptr)
       {
              m_ExitCallback->Invoke(this, m_id);
       }
}


5. 主页面处理回调函数

void SPT::MainPage::OnPageExit(Platform::Object^ sender, int id)
{
   for (int i = 0; i < BtnContainer->Children->Size; ++i)
   {
      if (Button^ btn = dynamic_cast<Button^>(BtnContainer->Children->GetAt(i)))
      {
          if (btn->Tag != nullptr && safe_cast<int>(btn->Tag) == id)
          {
               BtnContainer->Children->RemoveAt(i);
               return;
          }
       }
   }
}



 7、界面优化 

调整控件的边界Margin,用LinearGradientBrush设置页面及控件背景色,在代码中适当调整控件字体样式,使得界面满足设计需求。



 8、调试 

打开Win10 IoT板子上的调试助手。

3.png

选择工程平台为ARM64,编译运行,示例如下。

12.png

需要程序源码可以联系英创工程师获得。