Windows 10 IoT ARM64平台下,UWP应用和MFC程序不一样,同时只能打开一个应用实例。以串口程序为例,如果用户希望同时打开多个应用实例,一个应用实例打开串口1,一个应用实例打开串口2,那么我们可以加载多个页面,在各个页面分别加载功能模块,实现程序功能模块多开。
本文以UWP串口例程为例,介绍如何用代码实现同时打开多个串口模块页面,进行测试。
1. 打开visual studio 2022,点击“创建新项目”。
2. 选择筛选条件,语言C++,平台选择Windows,应用类型选择UWP。
3. 选择“空白应用”,本文以C++/CX为例。
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过来了。
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页面了。
为了便于管理打开的新页面,能在它们之间随意切换,可以使用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; }
这样,就可以通过点击不同按钮,进入对应的页面了
有时候,如果导航按钮的数量不确定,希望根据实际情况添加或删除导航按钮。本文例程中,每点击一次添加按钮,便添加一个串口测试页面,做法如下。
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; }
目前导航页面和加载的子页面相对独立,很多时候主界面需要传递数据给子页面,便于子页面初始化,甚至需要给子页面提供函数接口。子页面退出时也需要回调函数通知主页面,并返回处理后的数据。
本文串口例程中
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; } } } }
调整控件的边界Margin,用LinearGradientBrush设置页面及控件背景色,在代码中适当调整控件字体样式,使得界面满足设计需求。
打开Win10 IoT板子上的调试助手。
选择工程平台为ARM64,编译运行,示例如下。
需要程序源码可以联系英创工程师获得。
成都英创信息技术有限公司 028-8618 0660