Windows 10 IoT ARM64平台下,UWP应用和MFC程序不一样,同时只能打开一个应用实例。以串口程序为例,如果用户希望同时打开多个应用实例,一个应用实例打开串口1,一个应用实例打开串口2,那么我们可以加载多个页面,在各个页面分别加载功能模块,实现程序功能模块多开。
本文以UWP串口例程为例,介绍如何用代码实现同时打开多个串口模块页面,进行测试。
1. 创建UWP工程
1. 打开visual studio 2022,点击“创建新项目”。
2. 选择筛选条件,语言C++,平台选择Windows,应用类型选择UWP。
3. 选择“空白应用”,本文以C++/CX为例。
2. 导入需要多开的功能模块页面
1. 创建工程后,右键点击工程->添加->新建项。
2.选择XAML空白页,给页面命名为SerialPage.xaml,点击添加。
3. 在工程中可以看到添加的页面SerialPage,将之前写好的UWP串口程序代码COPY过来(参考文章《UWP串口程序开发》),把串口程序的xaml内容拷贝到SerialPage.xaml中,把代码文件.xaml.h和.xaml.cpp的内容也拷贝到对应文件SerialPage.xaml.h和SerialPage.xaml.cpp中。如果原UWP程序页面名称(默认为MainPage)和当前工程名称(SerialPage)不一样,注意全部替换一下。
4. 双击页面SerialPage.xaml,检查下是否添加成功。编译一下,确认代码都正确COPY过来了。
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板子上的调试助手。
选择工程平台为ARM64,编译运行,示例如下。
需要程序源码可以联系英创工程师获得。