Windows App开发之更多技巧

最后更新于:2022-04-01 16:23:49

# 使用华丽丽的字体 所有的TextBlock等都用的默认字体,大家是否会感觉很千篇一律很枯燥呢?对于FontFamily,我们见过一些可以用的字体,但这个属性不像Foreground等有下拉框,所以我们在应用中见过的许多有意思的字体却没法用,因为不知道名字。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a034028a350.jpg "") ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a03402a4b0d.jpg "") 代码的话也贴张图示意一下吧。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a03402c5c7e.jpg "") 好了,我就不再多说废话啦,名字都是从这里来的——>>>>> ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a03403003ac.jpg "") 注意: 1)除了微软雅黑外,大部分字体只能在Windows 8/8.1/10上体现出来,在WP8/8.1上无法发挥作用。这是因为这个字体库太大,微软没有放到手机上。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a03403238c5.jpg "") 2)大部分中文字体可以作用在英文文本上,而英文字体则无法作用在中文文本上。(经验之谈,如果特例,请告知,谢谢。) # DatePickerFlyout等的使用 这一篇讲解在WP上DataPickerFlyout和TimePickerFlyout的使用,但它们只放在WP上哦,也正因此才将这一节放到最后一章的。 ~~~ <Grid Background="Blue"> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <StackPanel Grid.Row="0" Margin="12" Orientation="Vertical"> <Button Content="Let's Show DatePicker" > <Button.Flyout> <DatePickerFlyout x:Name="datePickerFlyout" Title="选择日期" DatePicked="datePickerFlyout_DatePicked" Closed="datePickerFlyout_Closed" /> </Button.Flyout> </Button> <DatePicker Header="Date" Margin="4" /> <TextBlock Name="textBlockDate" FontSize="20" Margin="4" /> </StackPanel> <StackPanel Grid.Row="1" Margin="12" Orientation="Vertical"> <Button Content="Let's Show TimePicker" > <Button.Flyout> <TimePickerFlyout x:Name="timePickerFlyout" Title="选择时间" TimePicked="timePickerFlyout_TimePicked" Closed="timePickerFlyout_Closed" /> </Button.Flyout> </Button> <TimePicker Header="Time" Margin="4" /> <TextBlock Name="textBlockTime" FontSize="20" Margin="4"/> </StackPanel> </Grid> ~~~ 后台事件如下: ~~~ public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); this.NavigationCacheMode = NavigationCacheMode.Required; } protected override void OnNavigatedTo(NavigationEventArgs e) { // 令天数加1 datePickerFlyout.Date = DateTimeOffset.Now.AddDays(1); // 设置可选择的最大年份和最小年份 datePickerFlyout.MinYear = DateTimeOffset.Now.AddYears(-100); datePickerFlyout.MaxYear = DateTimeOffset.Now.AddYears(100); // 此处也可以令“年“、”月“、”日“中的某一个不显示 datePickerFlyout.YearVisible = true; datePickerFlyout.MonthVisible = true; datePickerFlyout.DayVisible = true; // 选择的历法 // (Gregorian 公历, Hebrew 希伯来历, Hijri 回历, Japanese 日本历, Julian 罗马儒略历, Korean 朝鲜历, Taiwan 台湾历, Thai 泰国历, UmAlQura 古兰经历) datePickerFlyout.CalendarIdentifier = CalendarIdentifiers.Gregorian; // Time - TimePicker 控件当前显示的时间 timePickerFlyout.Time = new TimeSpan(18, 0, 0); // 设置TimePickerFlyout的分钟选择框内数字的增幅 timePickerFlyout.MinuteIncrement=2; //设置为24小时制,也可以为12小时制 timePickerFlyout.ClockIdentifier = ClockIdentifiers.TwentyFourHour; } // 当用户点击DatePicker的完成按钮后激活该事件 private void datePickerFlyout_DatePicked(DatePickerFlyout sender, DatePickedEventArgs args) { textBlockDate.Text = args.NewDate.ToString("yyyy-MM-dd hh:mm:ss"); textBlockDate.Text += Environment.NewLine; } // 当用户点击DatePicker的取消按钮或手机的返回按钮后激活该事件,当点击完成按钮后也将调用该事件 private void datePickerFlyout_Closed(object sender, object e) { textBlockDate.Text += "You just close the DatePickerFlyout."; textBlockDate.Text += Environment.NewLine; } // 当用户点击TimePicker的完成按钮后激活该事件 private void timePickerFlyout_TimePicked(TimePickerFlyout sender, TimePickedEventArgs args) { // e.OldTime - 原时间 // e.NewTime - 新时间 textBlockTime.Text = args.NewTime.ToString("c"); textBlockTime.Text += Environment.NewLine; } // 当用户点击TimePicker的取消按钮或手机的返回按钮后激活该事件,当点击完成按钮后也将调用该事件 private void timePickerFlyout_Closed(object sender, object e) { textBlockTime.Text += "You just close the TimePickerFlyout."; textBlockTime.Text += Environment.NewLine; } } ~~~ 简单的讲,Flyout有两种创建方式,一种就是上面的通过Button的Flyout属性。另一种是通过FlyoutBase.AttachedFlyout属性给任何的FrameworkElement对象添加它。 关于FrameworkElement的更多知识,可以访问以下链接。 [https://msdn.microsoft.com/zh-cn/library/vstudio/system.windows.frameworkelement(v=vs.100).aspx](https://msdn.microsoft.com/zh-cn/library/vstudio/system.windows.frameworkelement(v=vs.100).aspx) [https://msdn.microsoft.com/en-us/library/system.windows.frameworkelement(v=vs.110).aspx](https://msdn.microsoft.com/en-us/library/system.windows.frameworkelement(v=vs.110).aspx) 而Flyout则有6种不同的类型:Flyout、DatePickerFlyout、ListPickerFlyout、MenuFlyout、TimePickerFlyout。 时间紧迫就直接Show代码了。 XAML代码: ~~~ <Page.Resources> <Style TargetType="Button"> <Setter Property="Margin" Value="12"/> <Setter Property="FontSize" Value="20"/> <Setter Property="Foreground" Value="White"/> <Setter Property="Background" Value="Green"/> </Style> </Page.Resources> <Grid Background="Blue"> <StackPanel Orientation="Vertical"> <!-- Flyout --> <Button Content="Let's Show Flyout"> <Button.Flyout> <Flyout> <StackPanel > <TextBox PlaceholderText="What do you want to say?"/> <Button HorizontalAlignment="Right" Content="Yup"/> </StackPanel> </Flyout> </Button.Flyout> </Button> <!-- DatePickerFlyout --> <Button Content="Let's Show DatePicker" HorizontalAlignment="Right"> <Button.Flyout> <DatePickerFlyout Title="You need to choose Date: " DatePicked="DatePickerFlyout_DatePicked"/> </Button.Flyout> </Button> <!-- ListPickerFlyout --> <Button Content="Let's Show ListPicker" > <Button.Flyout> <ListPickerFlyout x:Name="listPickerFlyout" Title="选择操作系统:" ItemsPicked="listPickerFlyout_ItemsPicked" > <ListPickerFlyout.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding}" FontSize="30"></TextBlock> </DataTemplate> </ListPickerFlyout.ItemTemplate> </ListPickerFlyout> </Button.Flyout> </Button> <!-- MenuFlyout --> <Button x:Name="menuFlyoutButton" Content="Let's Show MenuFlyout" HorizontalAlignment="Right"> <Button.Flyout > <MenuFlyout> <MenuFlyoutItem Text="You just say yes?" Click="MenuFlyoutItem_Click"/> <MenuFlyoutItem Text="You just say no?" Click="MenuFlyoutItem_Click"/> <MenuFlyoutItem Text="You say nothing..." Click="MenuFlyoutItem_Click"/> </MenuFlyout> </Button.Flyout> </Button> <!-- PickerFlyout --> <Button Content="Let's Show Picker" > <Button.Flyout> <PickerFlyout Confirmed="PickerFlyout_Confirmed" ConfirmationButtonsVisible="True"> <TextBlock Text="Are you ok?" FontSize="30" Margin="0 100 0 0"/> </PickerFlyout> </Button.Flyout> </Button> <!-- TimePickerFlyout --> <Button Content="Let's Show TimePicker" HorizontalAlignment="Right"> <Button.Flyout> <TimePickerFlyout Title="You need to choose Time: " TimePicked="TimePickerFlyout_TimePicked"/> </Button.Flyout> </Button> <!-- FlyoutBase --> <TextBlock Text="Game Over" Margin="12" Foreground="Wheat" Tapped="TextBlock_Tapped" FontSize="20"> <FlyoutBase.AttachedFlyout> <Flyout> <TextBox Text="哎哟,不错哦!"/> </Flyout> </FlyoutBase.AttachedFlyout> </TextBlock> </StackPanel> </Grid> ~~~ 后台C#代码: ~~~ public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); // 绑定List数据到ListPickerFlyout listPickerFlyout.ItemsSource = new List<string> { "Windows 10", "Windows 8/8.1", "Windows 7", "Windows Vista", "Windows XP","Others" }; } // DatePickerFlyout的日期选中事件,此处事件内有是包含日期的MessageDialog控件 private async void DatePickerFlyout_DatePicked(DatePickerFlyout sender, DatePickedEventArgs args) { await new MessageDialog(args.NewDate.ToString()).ShowAsync(); } // ListPickerFlyout的选中事件,选择列表中的一项后会以弹窗的方式显示出来 private async void listPickerFlyout_ItemsPicked(ListPickerFlyout sender, ItemsPickedEventArgs args) { if (sender.SelectedItem != null) { await new MessageDialog("You choose: " + sender.SelectedItem.ToString()).ShowAsync(); } } // MenuFlyout的菜单选项的点击事件,将选择的本文赋值给Content private void MenuFlyoutItem_Click(object sender, RoutedEventArgs e) { menuFlyoutButton.Content = (sender as MenuFlyoutItem).Text; } // PickerFlyout的确认事件,此处事件内有是包含字符串的MessageDialog控件 private async void PickerFlyout_Confirmed(PickerFlyout sender, PickerConfirmedEventArgs args) { await new MessageDialog("You choose ok").ShowAsync(); } // TimePickerFlyout的时间选中事件,此处事件内有是包含所选时间的MessageDialog控件 private async void TimePickerFlyout_TimePicked(TimePickerFlyout sender, TimePickedEventArgs args) { await new MessageDialog(args.NewTime.ToString()).ShowAsync(); } // 通过FlyoutBase.ShowAttachedFlyout方法来展示出Flyout控件 private void TextBlock_Tapped(object sender, TappedRoutedEventArgs e) { FrameworkElement element = sender as FrameworkElement; if (element != null) { FlyoutBase.ShowAttachedFlyout(element); } } } ~~~ 好了代码就到这里了,来几张截图。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a034033d937.jpg "") ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a034035ab8a.jpg "") ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a034037589d.jpg "") ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a034038cc0a.jpg "") ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a03403a31ca.jpg "")
';

Windows App开发之使用通知与动态磁贴

最后更新于:2022-04-01 16:23:47

# 使用Toast通知示例 前面我们使用了MessageDialog来作为弹窗,这里来介绍一个更加高大上的Toast通知。 Toast通知本质上动力是由XML来提供的,一开始我还不相信不知道XML原来有这么大的威力。现在就来看看和Toast相关的知识。 1)实例化ToastNotification类。 ~~~ ToastNotification toast1 = new ToastNotification(xdoc); ~~~ 2)使用ToastNotificationManager来管理Toast通知,包括添加、展示、移除、获取通知等等。 ~~~ ToastNotificationManager.CreateToastNotifier().Show(toast1); ~~~ 3)在第一步中的xdoc就是一段XML数据,它从何而来呢? ~~~ XmlDocument xdoc = new XmlDocument(); ~~~ 4)上一步代码实例化了一个XML,但是它没有数据呀,数据从哪来呢? ~~~ xdoc.LoadXml(txtXML.Text); ~~~ 5)这段代码就将一段Text导入了XML中。而Text数据有很多种获取方式。在下文中自然会提到。 Toast的XML模板有许多,我们可以直接来获取它们。用枚举和强大的var即可。 ~~~ var items = Enum.GetNames(typeof(ToastTemplateType)); ~~~ 那么就正式开工了,因为重复的属性太多了我就大概设置了2个Style资源。 ~~~ <Page.Resources> <Style TargetType="TextBlock" x:Key="StyleHeaderTextBlock"> <Setter Property="FontSize" Value="40"/> <Setter Property="FontFamily" Value="华文琥珀"/> <Setter Property="Foreground" Value="HotPink"/> <Setter Property="Margin" Value="12"/> </Style> <Style TargetType="Button" x:Key="StyleToastButton"> <Setter Property="Width" Value="180"/> <Setter Property="Height" Value="50"/> <Setter Property="Background" Value="Aqua"/> <Setter Property="FontSize" Value="21"/> <Setter Property="Margin" Value="12"/> <Setter Property="Content" Value="显示Toast通知" /> </Style> </Page.Resources> ~~~ 下面是第一部分用于生成Toast通知。 ~~~ <StackPanel Orientation="Vertical" Grid.Column="0" Margin="12"> <TextBlock Text="生成Toast通知" Style="{StaticResource StyleHeaderTextBlock}"/> <StackPanel Orientation="Horizontal" HorizontalAlignment="Left"> <TextBlock FontSize="24" Foreground="Wheat" Text="请选择一个模板:" VerticalAlignment="Center"/> <ComboBox Name="comboBoxToast" Foreground="Green" Width="275" SelectionChanged="comboBoxToast_SelectionChanged"/> </StackPanel> <TextBox Foreground="Green" x:Name="txtXML" HorizontalAlignment="Left" Width="500" Height="400" Header="模板XML:" TextWrapping="Wrap" FontSize="24"/> <Button Name="btnShowToast1" Click="btnShowToast1_Click" Style="{StaticResource StyleToastButton}"/> </StackPanel> ~~~ 后台代码也蛮容易的,利用上面讲的就好了。 ~~~ public MainPage() { this.InitializeComponent(); var items = Enum.GetNames(typeof(ToastTemplateType)); this.comboBoxToast.ItemsSource = items; } private void comboBoxToast_SelectionChanged(object sender, SelectionChangedEventArgs e) { string tempt = ((ComboBox)sender).SelectedItem as string; if (!string.IsNullOrEmpty(tempt)) { ToastTemplateType template = (ToastTemplateType)Enum.Parse(typeof(ToastTemplateType), tempt); XmlDocument xdoc = ToastNotificationManager.GetTemplateContent(template); txtXML.Text = xdoc.GetXml(); } } private void btnShowToast1_Click(object sender, RoutedEventArgs e) { if (txtXML.Text == "") return; XmlDocument xdoc = new XmlDocument(); xdoc.LoadXml(txtXML.Text); ToastNotification toast1 = new ToastNotification(xdoc); ToastNotificationManager.CreateToastNotifier().Show(toast1); } ~~~ 模板是这样用的…… ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a034021be6b.jpg "") ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a0340232677.jpg "") 在src中填入图片的路径也可以在Toast中显示图像哦,赶紧试试吧…… 接下来是第二段啦,和前面的很是类似…… ~~~ <StackPanel Orientation="Vertical" Grid.Column="1"> <TextBlock Text="更改Toast通知的提示音" Style="{StaticResource StyleHeaderTextBlock}"/> <TextBlock Margin="12" Text="请输入Toast消息内容:" FontSize="24"/> <TextBox Margin="12" Height="50" x:Name="txtMesaage"/> <TextBlock Margin="12" FontSize="24" Text="请选择一种提示声音:"/> <ComboBox Margin="12" Height="50" x:Name="comboBoxAudio" Width="400" HorizontalAlignment="Left"> <ComboBoxItem IsSelected="True">ms-winsoundevent:Notification.Default</ComboBoxItem> <ComboBoxItem>ms-winsoundevent:Notification.IM</ComboBoxItem> <ComboBoxItem>ms-winsoundevent:Notification.Mail</ComboBoxItem> <ComboBoxItem>ms-winsoundevent:Notification.Reminder</ComboBoxItem> <ComboBoxItem>ms-winsoundevent:Notification.Looping.Alarm</ComboBoxItem> <ComboBoxItem>ms-winsoundevent:Notification.Looping.Call</ComboBoxItem> </ComboBox> <StackPanel Orientation="Horizontal"> <CheckBox x:Name="checkBoxLoop" Margin="12" Content="循环播放"/> <CheckBox x:Name="checkBoxSilent" Margin="12" Content="静音"/> </StackPanel> <Button Name="btnShowToast2" Click="btnShowToast2_Click" Style="{StaticResource StyleToastButton}"/> </StackPanel> ~~~ 上面代码中的“ms-winsoundevent:Notification.Default”都是填到src中的用于设置声音,还可以在loop、silent中设置是否循环以及是否静音,那到底该怎么用呢?应该将这些属性全部都填入到XML中。 ~~~ xmlContent = string.Format( "<toast duration='{0}'>" + "<visual>" + "<binding template='ToastText01'>" + "<text id='1'>{1}</text>" + "</binding>" + "</visual>" + "<audio src='{2}' loop='{3}' silent='{4}'/>" + "</toast>", toastDuration, msg, audioSrc, isLoop, isSilent ); ~~~ 上面用的xmlContent也要先定义出来,一开始设置为Empty就好。 ~~~ string xmlContent = string.Empty; ~~~ isLoop和isSilent属性都可以借助于三目运算符在CheckBox中获取来。 ~~~ string isLoop = checkBoxLoop.IsChecked == true ? "true" : "false"; string audioSrc = (comboBoxAudio.SelectedItem as ComboBoxItem).Content.ToString(); string toastDuration = checkBoxLoop.IsChecked == true ? "long" : "short"; string isSilent = checkBoxSilent.IsChecked == true ? "true" : "false"; ~~~ 当然,考虑得更加周到些,用户可以在还没有输入通知内容就点了显示Toast通知按钮,对此用三目运算符也是极好的选择。 ~~~ string msg = txtMesaage.Text == "" ? "你还没有输入Toast通知的内容呢……" : txtMesaage.Text; ~~~ 这些准备工作都写好了以后呢就该设置Toast通知了,和上面的Toast1类似哦,大家试试。 可是这些通知都没有时间性可言,因为有时候我们需要定在一个时间来执行Toast通知。这自然也是可以实现的。 先作如下界面设计。 ~~~ <StackPanel Orientation="Vertical" Grid.Column="2"> <TextBlock Text="计划时间显示Toast通知" Style="{StaticResource StyleHeaderTextBlock}"/> <StackPanel Orientation="Horizontal" Height="60"> <TextBlock FontSize="28" Text="计划在" VerticalAlignment="Center"/> <TextBox Name="tBoxTime" FontSize="28" Width="60" Height="45" VerticalAlignment="Center"/> <TextBlock FontSize="28" Text="秒后显示Toast通知" VerticalAlignment="Center"/> </StackPanel> <Button Name="btnShowToast3" Click="btnShowToast3_Click" Style="{StaticResource StyleToastButton}"/> </StackPanel> ~~~ 后台代码如下。 ~~~ private async void btnShowToast3_Click(object sender, RoutedEventArgs e) { int toastTime; try { toastTime = int.Parse(tBoxTime.Text.ToString()); XmlDocument xdoc = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText01); var txtnodes = xdoc.GetElementsByTagName("text"); txtnodes[0].InnerText = "你好,这是一条定时为"+toastTime.ToString()+ "秒的Toast消息。"; ScheduledToastNotification toast3 = new ScheduledToastNotification(xdoc, DateTimeOffset.Now.AddSeconds(toastTime)); ToastNotificationManager.CreateToastNotifier().AddToSchedule(toast3); } catch (Exception ex) { Windows.UI.Popups.MessageDialog messageDialog = new Windows.UI.Popups.MessageDialog(ex.Message); await messageDialog.ShowAsync(); } } ~~~ 在这个小程序中因为侧重于讲解定时而非Toast的通知样式,因此就选用了比较简单的ToastText01模板。而后找出Text节点,并向该节点写入内容。最后就是创建Toast通知了。 ~~~ ScheduledToastNotification toast3 = new ScheduledToastNotification(xdoc, DateTimeOffset.Now.AddSeconds(toastTime)); ~~~ 同样为了防止用户没有在TextBox中输入时间或输入了错误格式的时间比如“5秒”而做了try、catch异常检测。当然了,在实际产品中,这里可就要做得更加完美了,时间用TextBox来输入并不是一件好事,而应用我们前面介绍的TimePicker。 给这3段程序来张全家福吧~ ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a034024ee8e.jpg "") # 使用动态磁贴示例 动态磁贴是什么,相信大家用了这么久的Windows 8/8.1/10早就非常了解了吧。 像什么小磁贴、中磁贴、宽磁贴、大磁贴,还有这里的应用商店Logo等,大家在下面根据不同的分辨率选择合适的图片就好啦。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a034026f6f8.jpg "") 下面来做一个更新磁贴页面的功能,这是页面XML部分。 ~~~ <StackPanel Margin="12"> <StackPanel Orientation="Horizontal"> <TextBlock FontSize="28" Text="选择模板:" VerticalAlignment="Center"/> <ComboBox x:Name="comboBoxTile" Width="400" SelectionChanged="comboBoxTile_SelectionChanged"/> </StackPanel> <TextBox x:Name="textBoxXML" TextWrapping="Wrap" FontSize="22" Header="XML文档" Width="420" Height="320" HorizontalAlignment="Left" Margin="12"/> <Button Name="btnTile" Content="更新磁贴" Click="btnTile_Click" Style="{StaticResource StyleToastButton}"/> </StackPanel> ~~~ 在后台代码的Main函数中,获取TileTemplateType枚举并绑定到ComboBox上。 ~~~ var itemsTile = Enum.GetNames(typeof(TileTemplateType)); this.comboBoxTile.ItemsSource = itemsTile; ~~~ 下面的代码和前面的Toast真的非常类似,所以我才把这两节连在一起来写了。Button按钮的Click事件中,和之前一样建一个XML,然后加载到TileNotification类的实例中。最后就是TileUpdateManager类,也就是磁贴更新。 ~~~ private void btnTile_Click(object sender, RoutedEventArgs e) { if (this.textBoxXML.Text == "") return; XmlDocument xdoc = new XmlDocument(); xdoc.LoadXml(this.textBoxXML.Text); TileNotification tileNotifi = new TileNotification(xdoc); TileUpdateManager.CreateTileUpdaterForApplication().Update(tileNotifi); } private void comboBoxTile_SelectionChanged(object sender, SelectionChangedEventArgs e) { TileTemplateType tileTemplate = (TileTemplateType)Enum.Parse(typeof(TileTemplateType), this.comboBoxTile.SelectedItem as string); XmlDocument xdoc = TileUpdateManager.GetTemplateContent(tileTemplate); this.textBoxXML.Text = xdoc.GetXml(); } ~~~ 当然了,如果你的APP不满足于一个磁贴,你也可以创建第二个磁贴哟! 依旧和Toast通知的XML类似,它也有好多属性的…… Arguments:使用该字符串参数在通过次要磁贴启动应用程序时会传递给Application类的OnLaunched方法,这样一来应用程序就可以根据传入的参数来执行特定的操作。 BackgroundColor:设置磁贴的背景色。 DisplayName和ShortName:设置显示在磁贴上的文本。 Logo等:设置磁贴的图标,用Uri。 ForegroundText:磁贴上文本的颜色,可用的选项有深色、浅色等。 TileID:设置磁贴的唯一标识ID,创建新磁贴前用SecondaryTile.Exists判断是否已经存在。 在添加第二磁贴的Button的Click事件中: ~~~ private async void btnCreateTile(object sender, RoutedEventArgs e) { if(SecondaryTile.Exists(textTileID.Text)) { textBlockMsg.Text="该ID磁贴已经存在"; return ; } Uri uriImg=new Uri("ms-appx:///Assests/uriImg.png"); …… …… // 创建第二磁贴 SecondaryTile secTile=new SecondaryTile(); this.Tag=secTile; secTile.DisplayName=textBlockDisplayName.Text; secTile.TileID=textBlockID.Text; secTile.Arguments="second"; // 在后面有用到 // 设置图标 secTile.VisualElements.BackgroundColor=Windows.UI.Colors.Gold; …… …… bool r=await secTile.RequestCreateAsync(); textBlockMsg.Text=r == true ?"磁贴创建成功啦.":"磁贴创建失败了哎."; // 返回测试结果 ~~~ 如果希望点击第二磁贴导航到特定的页面,就需要重写该页面的OnNavigatedTo方法。 ~~~ preteced async override void OnNavigatedTo(NavigationEventArgs e) { if(e.Parameter is Windows.ApplicationModel.Activation.LaunchActivatedEventArgs) { var arg=e.Parameter as Windows.ApplicationModel.Activation.LaunchActivateEventArgs; …… } } if(rootFrame.Content==null) { if(e.Arguments=="second") rootFrame.Navigate(typeof(OtherPage),e); else rootFrame.Navigate(typeof(MainPage)); } ~~~ 这里的参数”second”就是上面设置那个Arguments哦,它的作用就在于这里呢。
';

Windows App开发之集成设置、帮助、搜索和共享

最后更新于:2022-04-01 16:23:44

# 应用设置和应用帮助 ### ”设置“合约 上一节中我们学习了如何将应用设置保存到本地,这种方式是通过在App内添加设置选项,这里还有一种方式。微软将其称为“设置”合约,并且所有的Windows应用商店应用都将自动配合这种合约。但是应用自带的这种设置如果不做任何修改可谓毫无作用。而我们添加这些设置则可以让应用更加个性化哦。 ### SettingsFlyout 首先新建一个SettingsFlyout页面,也许很多童鞋会像我当初学这个一样立马就调试程序等着看看这个设置是长什么样,不过现在还用不了哦。 如下所示,我们可以修改IconSource来改变”设置“中的图标。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a034005485b.jpg "") 然后我将设置界面的布局设置如下咯。 ~~~ <StackPanel VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Orientation="Vertical"> <StackPanel Orientation="Vertical" > <TextBlock Text="Big Car 的美好一天" FontSize="28" Foreground="Red" Margin="12"/> <TextBlock Text="购买一辆Big Car会让你的生活充满活力,充满激情!" FontSize="20" Margin="12" TextWrapping="Wrap" Foreground="Black"/> <TextBlock Text="想购买的话可以直接发邮件 nomasp@outlook.com" FontSize="20" Margin="12" Foreground="Gold" TextWrapping="Wrap"/> </StackPanel> <StackPanel Orientation="Vertical" Margin="8"> <ToggleSwitch x:Name="toggleSwitch1" Header="每日更新Big Car的最新图片" OnContent="On" OffContent="Off" Toggled="ToggleSwitch_Toggled" /> <ToggleSwitch x:Name="toggleSwitch2" Header="向我推送相关的动态" OnContent="On" OffContent="Off" Toggled="ToggleSwitch_Toggled" IsOn="True"/> </StackPanel> <StackPanel Orientation="Vertical" Margin="0,12,0,12"> <Button Content="好评该应用呗" Margin="12"/> <Button Content="清除所有缓存" Margin="12"/> </StackPanel> </StackPanel> ~~~ ### App.xaml.cs 先在app.xaml.cs中添加下面这条命名空间,和以下3个方法 ~~~ using Windows.UI.ApplicationSettings; ~~~ ~~~ protected override void OnWindowCreated(WindowCreatedEventArgs args) { SettingsPane.GetForCurrentView().CommandsRequested += OnCommandsRequested; } private void OnCommandsRequested(SettingsPane sender,SettingsPaneCommandsRequestedEventArgs args) { args.Request.ApplicationCommands.Add(new SettingsCommand("BigCarMainSettings", "Big Car 的主要设置", (handler) => ShowCustomSettingFlyout())); } public void ShowCustomSettingFlyout() { BigCarSettings CustomSettingFlyout = new BigCarSettings(); CustomSettingFlyout.Show(); } ~~~ ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a034006d46a.jpg "") ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a034007eda4.jpg "") ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a034009cad0.jpg "") 当然了,在那些控件中的点击啥的最后都要在后台代码中添加的,就像上一篇博客那样来保存设置就好啦。 以上就是关于应用设置同样的内容咯,而应用帮助嘛,和这些都是一样的呀。创建同样的目标就好了。然后在XAML中修改成自己喜欢的样子就好啦。而且和应用设置一样,我们也可以在底部设置应用栏的,关于应用栏的内容可以查看第三章的“应用栏”一节。 ~~~ protected override void OnWindowCreated(WindowCreatedEventArgs args) { SettingsPane.GetForCurrentView().CommandsRequested += OnCommandsRequested; } private void OnCommandsRequested(SettingsPane sender, SettingsPaneCommandsRequestedEventArgs args) { args.Request.ApplicationCommands.Add(new SettingsCommand("BigCarHelp", "Big Car 的帮助", (handler) => ShowHelpSettingsFlyout())); } public void ShowHelpSettingsFlyout() { BigCarHelphelpSF = new BigCarHelp(); helpSF.Show(); } ~~~ # 在应用中集成搜索 上一节是关于如何添加应用设置和帮助,这一篇讲的是和设置类似的搜索。 So…… Let’s do it ! 先从简单的页面布局开始,想想我们需要什么,一个带搜索事件的Button,还需要一些TextBlock来提示用户,核心部分自然是一个GridView咯。 ~~~ <Grid Background="Wheat"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition /> </Grid.RowDefinitions> <StackPanel Grid.Row="0" Orientation="Vertical"> <Button Grid.Row="0" Name="btnSearch" VerticalAlignment="Center" HorizontalAlignment="Left" Content="搜索" FontFamily="华文行楷" Click="btnSearch_Click" Margin="12" FontSize="34" Foreground="Red"/> <StackPanel Orientation="Horizontal"> <TextBlock Text="搜索关键词" Foreground="Green" FontSize="28" Margin="12"/> <TextBlock FontSize="28" Foreground="Green" Name="tBlockKeyword" Margin="12"/> </StackPanel> </StackPanel> <GridView Grid.Row="1" Margin="12" x:Name="gridView"> <GridView.ItemsPanel> <ItemsPanelTemplate> <WrapGrid Orientation="Horizontal"/> </ItemsPanelTemplate> </GridView.ItemsPanel> <GridView.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding}" FontSize="24" Foreground="Pink" FontFamily="楷体"/> </DataTemplate> </GridView.ItemTemplate> </GridView> </Grid> ~~~ 既然界面完成了,就该去后台捣鼓咯。搜索的核心在于SearchPane,所以先来实例化它。为了简化,我们就将待搜索的内容设置为一串字符串数组好了,当然了,初始化数组的方式大家随意就好了。 ~~~ SearchPane searchPane = null; string[] exampleStr = new string[100]; public void InitExampleStr() { Random ran = new Random(); int exNumber; for(int i=0;i<100;i++) { exNumber = ran.Next(1000, 9999); exampleStr[i] = exNumber.ToString(); } } ~~~ 当用户在搜索框中输入的内容发生了更改时就会触发searchPane_QueryChange事件。 当用户在完成输入后按下Enter键或者点击旁边的搜索确认按钮后就会触发searchPane_QuerySubmitted事件。 ~~~ void searchPane_QueryChanged(SearchPane sender, SearchPaneQueryChangedEventArgs args) { this.tBlockKeyword.Text = args.QueryText; } void searchPane_QuerySubmitted(SearchPane sender, SearchPaneQuerySubmittedEventArgs args) { string key = args.QueryText; var result = exampleStr.Where(s => s.Contains(key)).ToArray(); this.gridView.ItemsSource = result; } ~~~ 然后我们还需要这两个事件在OnNavigatedTo中绑定以及在OnNavigatedFrom中解绑。 ~~~ protected override void OnNavigatedTo(NavigationEventArgs e) { this.searchPane.QuerySubmitted += searchPane_QuerySubmitted; this.searchPane.QueryChanged += searchPane_QueryChanged; } protected override void OnNavigatedFrom(NavigationEventArgs e) { this.searchPane.QuerySubmitted -= searchPane_QuerySubmitted; this.searchPane.QueryChanged -= searchPane_QueryChanged; } ~~~ 然后我们需要点击Button控件来调出系统的搜索框,一行代码就足以搞定了。如果不想点击按钮也是可以得哦,可以让用户直接在键盘输入而调出搜索框呢。 ~~~ private void btnSearch_Click(object sender, RoutedEventArgs e) { this.searchPane.Show(); } ~~~ ~~~ this.searchPane.ShowOnKeyboardInput = true; ~~~ 最后别忘了将他们都放到MainPage()中哦, ~~~ public MainPage() { this.InitializeComponent(); searchPane = SearchPane.GetForCurrentView(); InitExampleStr(); this.searchPane.PlaceholderText = "请输入关键字"; this.searchPane.ShowOnKeyboardInput = true; } ~~~ 所以说,总的代码是这样的。 ~~~ SearchPane searchPane = null; string[] exampleStr = new string[100]; public MainPage() { this.InitializeComponent(); searchPane = SearchPane.GetForCurrentView(); InitExampleStr(); this.searchPane.PlaceholderText = "请输入关键字"; this.searchPane.ShowOnKeyboardInput = true; } public void InitExampleStr() { Random ran = new Random(); int exNumber; for(int i=0;i<100;i++) { exNumber = ran.Next(1000, 9999); exampleStr[i] = exNumber.ToString(); } } protected override void OnNavigatedTo(NavigationEventArgs e) { this.searchPane.QuerySubmitted += searchPane_QuerySubmitted; this.searchPane.QueryChanged += searchPane_QueryChanged; } protected override void OnNavigatedFrom(NavigationEventArgs e) { this.searchPane.QuerySubmitted -= searchPane_QuerySubmitted; this.searchPane.QueryChanged -= searchPane_QueryChanged; } void searchPane_QueryChanged(SearchPane sender, SearchPaneQueryChangedEventArgs args) { this.tBlockKeyword.Text = args.QueryText; } void searchPane_QuerySubmitted(SearchPane sender, SearchPaneQuerySubmittedEventArgs args) { string key = args.QueryText; var result = exampleStr.Where(s => s.Contains(key)).ToArray(); this.gridView.ItemsSource = result; } private void btnSearch_Click(object sender, RoutedEventArgs e) { this.searchPane.Show(); } } ~~~ 在清单文件中声明你需要使用“Search”功能后就可以开始调试咯。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a03400b27d5.jpg "") ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a03400c7595.jpg "") 大家肯定都用的音乐播放器肯定都会在搜索框下面给出一些建议吧,或者大家常用的地图等App。 那么我们就对前面的代码进行更新就好啦。 下面这段代码呢,就是根据用户的输入来显示建议列表的方法咯。 ~~~ void searchPane_SuggestionsRequested(SearchPane sender, SearchPaneSuggestionsRequestedEventArgs args) { var deferralSeg= args.Request.GetDeferral(); var q = from i in exampleStr where i.Contains(args.QueryText) select i; var res = q.Take(suggestionLen).ToArray(); foreach (var item in res) { args.Request.SearchSuggestionCollection.AppendQuerySuggestion(item); } deferralSeg.Complete(); } ~~~ 这篇博客,使用大量LINQ技术,如果不太懂的话可以看看这里。 [【LINQ技术】扩展特性和LINQ操作符](http://blog.csdn.net/nomasp/article/details/45461517):[http://blog.csdn.net/nomasp/article/details/45461517](http://blog.csdn.net/nomasp/article/details/45461517) 使用搜索建议的最大好处在于我们可以选择并非自己输入的内容,这个功能就由下面这段代码提供动力支持。 ~~~ void searchPane_ResultSuggestionChosen(SearchPane sender, SearchPaneResultSuggestionChosenEventArgs args) { sender.TrySetQueryText(args.Tag); var q = from t in exampleStr where t.Contains(args.Tag) select t; this.gridView.ItemsSource = q.ToArray(); } ~~~ 我们还可以对前面的searchPane_QuerySubmitted函数做如下修改。 ~~~ void searchPane_QuerySubmitted(SearchPane sender, SearchPaneQuerySubmittedEventArgs args) { //var q = from extStr in exampleStr // where extStr.Contains(args.QueryText) // select extStr; //this.gridView.ItemsSource = q.ToArray(); string key = args.QueryText; var result = exampleStr.Where(s => s.Contains(key)).ToArray(); this.gridView.ItemsSource = result; } ~~~ 最后还需要将他们添加到OnNavigatedTo和OnNavigatedFrom方法中。 ~~~ protected override void OnNavigatedTo(NavigationEventArgs e) { this.searchPane.QuerySubmitted += searchPane_QuerySubmitted; this.searchPane.QueryChanged += searchPane_QueryChanged; this.searchPane.SuggestionsRequested += searchPane_SuggestionsRequested; this.searchPane.ResultSuggestionChosen += searchPane_ResultSuggestionChosen; } protected override void OnNavigatedFrom(NavigationEventArgs e) { this.searchPane.QuerySubmitted -= searchPane_QuerySubmitted; this.searchPane.QueryChanged -= searchPane_QueryChanged; this.searchPane.SuggestionsRequested -= searchPane_SuggestionsRequested; this.searchPane.ResultSuggestionChosen -= searchPane_ResultSuggestionChosen; } ~~~ 然后调试就会是这个效果咯。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a03400dd2f9.jpg "") # 使用粘贴板 记得智能手机刚出来那会比较火的一个概念“能够复制粘贴的手机就是智能手机”。现在看来,这不过是个老掉牙的功能了,但实际用处却是非常强大的,那么现在我们就来试试怎么做到这个功能。 粘贴板的英文名叫做Clipboard,这也是它的类名了。 新建工程这种就不说了,在XAML中代码如下: ~~~ <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}"> <Grid Margin="12" HorizontalAlignment="Left" VerticalAlignment="Top" Width="500" Height="500"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Button Grid.Row="0" Name="btnClip" Margin="0,3,0,16" Content="粘贴" FontSize="32" Click="btnClip_Click" IsEnabled="False"/> <ScrollViewer Name="scrollView" Grid.Row="1" Visibility="Collapsed"> <TextBlock Margin="12" Name="tBlockClipboard" FontSize="35" Foreground="Gainsboro" TextWrapping="Wrap" /> </ScrollViewer> </Grid> </Grid> ~~~ 在后台代码中写上这么一个方法: ~~~ void Clipboard_ContentChanged(object sender, object e) { DataPackageView pv = Clipboard.GetContent(); if (pv.Contains(StandardDataFormats.Text)) { btnClip.IsEnabled = true; } } ~~~ StandardDataFormats是标准数据格式,这里判断它是否是Text,如果是的话则让前面的Button按钮可用(之前设为不可用,以灰色显示)。 标准数据格式有Bitmap,HTML,RTF,StorageItems,Text,Uri等。 然后在按钮的Click事件中写如下代码: ~~~ private async void btnClip_Click(object sender, RoutedEventArgs e) { var txt = await Clipboard.GetContent().GetTextAsync(); tBlockClipboard.Text = txt; } ~~~ 这里我们使用了Clipboard类的GetContent()方法,用于在剪切板中取出DataPackageView对象数据;类似的还有SetContent(),用于把数据存入剪切板中。还有Clear事件来清空剪切板,Flush事件把数据从源写入到剪切板,并且在应用程序退出后依然保留在剪切板中。还有ContentChanged事件在剪切板中存储的数据内容发生变化时自动激活以达到监听剪切板内容变化的效果。 ~~~ protected override void OnNavigatedTo(NavigationEventArgs e) { Clipboard.ContentChanged += Clipboard_ContentChanged; } protected override void OnNavigatedFrom(NavigationEventArgs e) { Clipboard.ContentChanged -= Clipboard_ContentChanged; } void Clipboard_ContentChanged(object sender, object e) { DataPackageView pv = Clipboard.GetContent(); if (pv.Contains(StandardDataFormats.Text)||pv.Contains(StandardDataFormats.Bitmap)) { btnClip.IsEnabled = true; } } ~~~ 大家可以试试,已经完成了,但我们可以做的更多,不是吗? 完整的代码如下: ~~~ <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}"> <Grid Margin="12" HorizontalAlignment="Left" VerticalAlignment="Top" Width="500" Height="500"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Button Grid.Row="0" Name="btnClip" Margin="0,3,0,16" Content="粘贴" FontSize="32" Click="btnClip_Click" IsEnabled="False"/> <ScrollViewer Name="scrollView" Grid.Row="1" Visibility="Collapsed"> <TextBlock Margin="12" Name="tBlockClipboard" FontSize="35" Foreground="Gainsboro" TextWrapping="Wrap" /> </ScrollViewer> <Image x:Name="imgClicpboard" Grid.Row="1" Margin="5" Stretch="Uniform" Visibility="Collapsed"/> </Grid> </Grid> ~~~ ~~~ public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); } protected override void OnNavigatedTo(NavigationEventArgs e) { Clipboard.ContentChanged += Clipboard_ContentChanged; } protected override void OnNavigatedFrom(NavigationEventArgs e) { Clipboard.ContentChanged -= Clipboard_ContentChanged; } void Clipboard_ContentChanged(object sender, object e) { DataPackageView pv = Clipboard.GetContent(); if (pv.Contains(StandardDataFormats.Text)||pv.Contains(StandardDataFormats.Bitmap)) { btnClip.IsEnabled = true; } } private async void btnClip_Click(object sender, RoutedEventArgs e) { scrollView.Visibility = Visibility.Collapsed; imgClicpboard.Visibility = Visibility.Collapsed; tBlockClipboard.Text = " "; imgClicpboard.Source = null; DataPackageView pv = Clipboard.GetContent(); if (pv.Contains(StandardDataFormats.Text)) { scrollView.Visibility = Visibility; var txt = await Clipboard.GetContent().GetTextAsync(); tBlockClipboard.Text = txt; } else if(pv.Contains(StandardDataFormats.Bitmap)) { imgClicpboard.Visibility = Visibility; var bmp = await Clipboard.GetContent().GetBitmapAsync(); Windows.UI.Xaml.Media.Imaging.BitmapImage bitMap = new Windows.UI.Xaml.Media.Imaging.BitmapImage(); bitMap.SetSource(await bmp.OpenReadAsync()); this.imgClicpboard.Source = bitMap; } } } ~~~ 现在它还可以复制图片了哦~ ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a034010159a.jpg "") # 设置共享(共享源和共享目标) 上一节简单介绍了通过粘贴板来共享数据,这一节将会添加更为强大的功能哦。 以下就是大概的样式了,随便看看就好了,这都不是重点。 ~~~ <Grid Background="AliceBlue"> <Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition /> <RowDefinition Height="auto"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" FontSize="25" Foreground="Red" Text="共享源示例" Margin="12"/> <ScrollViewer Grid.Row="1" Margin="14" VerticalScrollMode="Auto" HorizontalScrollMode="Disabled"> <StackPanel> <StackPanel Orientation="Horizontal"> <RadioButton x:Name="radioBtnText" Foreground="Aqua" FontWeight="Bold" FontSize="22" Content="共享文本" Checked="OnChecked" GroupName="g" Tag="text"/> <RadioButton x:Name="radioBtnImg" Foreground="Aqua" FontWeight="Bold" FontSize="22" Content="共享图像" Margin="12,0,0,0" Checked="OnChecked" GroupName="g" Tag="image"/> <RadioButton x:Name="radioBtnFile" Foreground="Aqua" FontWeight="Bold" FontSize="22" Content="共享文件" Margin="12,0,0,0" Checked="OnChecked" GroupName="g" Tag="file"/> </StackPanel> <StackPanel Name="stackPText" Visibility="Collapsed" Margin="8"> <TextBlock Text="共享文本" FontSize="25"/> <TextBlock Foreground="Gold" FontSize="25" Text="输入要共享的内容" /> <TextBox x:Name="tBlockText" Foreground="Gold" /> </StackPanel> <StackPanel Name="stackPImg" Visibility="Collapsed" Margin="8"> <TextBlock Text="共享图像" FontSize="25"/> <TextBlock Foreground="Gold" FontSize="25" Text="共享以下图片"/> <Image Width="600" Height="400" Stretch="UniformToFill" HorizontalAlignment="Left" Margin="1,5,0,5" Source="Assets/SpaceNeedle1.jpg"/> </StackPanel> <StackPanel Name="stackPFile" Visibility="Collapsed" Margin="8"> <TextBlock Text="共享文件" FontSize="28"/> <TextBlock Foreground="Gold" FontSize="25" Text="选择要共享的文件"/> <StackPanel> <Button Content="选择文件" Click="OnPickFile"/> <TextBlock x:Name="tBlockFile" Foreground="Gold" FontSize="24"/> </StackPanel> </StackPanel> </StackPanel> </ScrollViewer> <Button Grid.Row="2" Name="btnShare" Margin="12" Content="确定共享" FontSize="35" FontFamily="隶书" Foreground="Azure" Background="Black" Click="btnShare_Click"/> </Grid> ~~~ 这里通过3个StackPanel的“显示“与”隐藏“来达到在一个位置显示3个界面的功能,然后在后台通过以下方法更改Visibility属性。 ~~~ private void OnChecked(object sender, RoutedEventArgs e) { RadioButton rbtn = sender as RadioButton; if (rbtn != null) { string tag = rbtn.Tag.ToString(); switch (tag) { case "text": this.stackPText.Visibility = Windows.UI.Xaml.Visibility.Visible; this.stackPImg.Visibility = Windows.UI.Xaml.Visibility.Collapsed; this.stackPFile.Visibility = Windows.UI.Xaml.Visibility.Collapsed; break; case "image": this.stackPText.Visibility = Windows.UI.Xaml.Visibility.Collapsed; this.stackPImg.Visibility = Windows.UI.Xaml.Visibility.Visible; this.stackPFile.Visibility = Windows.UI.Xaml.Visibility.Collapsed; break; case "file": this.stackPText.Visibility = Windows.UI.Xaml.Visibility.Collapsed; this.stackPImg.Visibility = Windows.UI.Xaml.Visibility.Collapsed; this.stackPFile.Visibility = Windows.UI.Xaml.Visibility.Visible; break; default: this.stackPText.Visibility = Windows.UI.Xaml.Visibility.Visible; this.stackPImg.Visibility = Windows.UI.Xaml.Visibility.Collapsed; this.stackPFile.Visibility = Windows.UI.Xaml.Visibility.Collapsed; break; } } } ~~~ 以下是核心代码,通过RadioButton的选择来共享不同的内容。这里没有进行try、catch异常检测,但在实际工程中则是必要的,因为如果你不共享任何内容而点击共享按钮你就知道了…… ~~~ void MainPage_DataRequested(DataTransferManager sender, DataRequestedEventArgs args) { var deferral = args.Request.GetDeferral(); if (radioBtnText.IsChecked == true) { args.Request.Data.Properties.Title = "共享文本"; args.Request.Data.Properties.Description = "共享你输入的文本数据。"; args.Request.Data.SetText(this.tBlockText.Text); } else if (radioBtnImg.IsChecked == true) { args.Request.Data.Properties.Title = "共享图像"; args.Request.Data.Properties.Description = "共享以下图片。"; args.Request.Data.SetBitmap(Windows.Storage.Streams.RandomAccessStreamReference.CreateFromUri(new Uri("ms-appx:///Assets/SpaceNeedle1.jpg"))); } else if (radioBtnFile.IsChecked == true) { args.Request.Data.Properties.Title = "共享文件"; args.Request.Data.Properties.Description = "共享你选择的文件。"; var file = this.tBlockFile.Tag as Windows.Storage.StorageFile; List<IStorageItem> files = new List<IStorageItem>(); files.Add(file); args.Request.Data.SetStorageItems(files); } deferral.Complete(); } ~~~ 选择文件的方法我们在前面也都介绍过了,直接贴代码…… ~~~ private async void OnPickFile(object sender, RoutedEventArgs e) { Windows.Storage.Pickers.FileOpenPicker picker = new Windows.Storage.Pickers.FileOpenPicker(); picker.FileTypeFilter.Add(".mp3"); picker.FileTypeFilter.Add(".jpg"); picker.FileTypeFilter.Add(".png"); picker.FileTypeFilter.Add(".docx"); picker.FileTypeFilter.Add(".pptx"); picker.FileTypeFilter.Add(".txt"); Windows.Storage.StorageFile file = await picker.PickSingleFileAsync(); if (file != null) { this.tBlockFile.Text = file.Path; this.tBlockFile.Tag = file; } } ~~~ 当然了,记得下面这些操作…… ~~~ protected override void OnNavigatedTo(NavigationEventArgs e) { DataTransferManager.GetForCurrentView().DataRequested += MainPage_DataRequested; } protected override void OnNavigatedFrom(NavigationEventArgs e) { DataTransferManager.GetForCurrentView().DataRequested -= MainPage_DataRequested; } ~~~ 最后就是共享确认按钮了,一行代码搞定。 ~~~ private void btnShare_Click(object sender, RoutedEventArgs e) { DataTransferManager.ShowShareUI(); } ~~~ 以上这个App,你将需要共享的数据从这里发出,也叫共享源,但共享到哪里了呢? ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a034011e6a6.jpg "") 看到”共享图像“和”共享以下图片“想起来刚才的两行代码了么?这两个属性就用在了这里。 ~~~ args.Request.Data.Properties.Title = "共享文本"; args.Request.Data.Properties.Description = "共享你输入的文本数据。"; ~~~ 我们当然可以将数据共享到邮件、OneNote里,但如果你是要写一个自己的接收共享数据的应用呢,如何来写? 接下来就来写另一个App咯,也就是上图中的App49了。首先在清单文件中做如下操作,当然了,具体要添加哪些东西大家自己看着办就好了。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a034013236e.jpg "") ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a034014a90b.jpg "") 然后添加一个XAML页面来接收数据,因为你不可能只让你的APP专门用来接收数据咯,所以就不建议在MainPage中写了。 在新页面中大概做一下页面布局,我的布局通常来说都不是很美观的…… ~~~ <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}"> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="100"/> </Grid.RowDefinitions> <Grid x:Name="gridText" Margin="24" Visibility="Collapsed" Grid.Row="0"> <StackPanel> <TextBlock FontSize="25" Foreground="Red" Text="接收到的文本:"/> <TextBlock FontSize="30" Foreground="Pink" FontWeight="Bold" x:Name="tbText" Margin="8"/> </StackPanel> </Grid> <Grid x:Name="gridImg" Margin="25" Visibility="Collapsed" Grid.Row="0"> <StackPanel> <TextBlock FontSize="25" Foreground="Red" Text="接收到的图像:"/> <Image x:Name="img" Margin="12" Width="500" Height="400" HorizontalAlignment="Left" Stretch="Uniform"/> </StackPanel> </Grid> <Grid x:Name="gridStorageItems" Margin="25" Visibility="Collapsed" Grid.Row="0"> <StackPanel> <TextBlock FontSize="25" Foreground="Red" Text="接收到的文件:"/> <TextBlock FontSize="30" Margin="12" x:Name="tbStorageItem"/> </StackPanel> </Grid> <Button Grid.Row="1" HorizontalAlignment="Center" Margin="0,15,0,20" Content="完成共享" FontSize="28" Width="200" Click="btnCompleteShare_Click"/> </Grid> ~~~ 后台代码中写以下代码,核心在于if中的3个判断,就是3中共享的文件了咯。 ~~~ public sealed partial class ShareTargetPage : Page { ShareOperation shareOperation = null; public ShareTargetPage() { this.InitializeComponent(); } protected async override void OnNavigatedTo(NavigationEventArgs e) { ShareOperation sp = e.Parameter as ShareOperation; if (sp != null) { this.shareOperation = sp; DataPackageView pack = sp.Data; if (pack.Contains(StandardDataFormats.Text)) { string s = await pack.GetTextAsync(); this.tbText.Text = s; this.gridText.Visibility = Windows.UI.Xaml.Visibility.Visible; } else if (pack.Contains(StandardDataFormats.Bitmap)) { var stream = await pack.GetBitmapAsync(); BitmapImage bmp = new BitmapImage(); bmp.SetSource(await stream.OpenReadAsync()); this.img.Source = bmp; this.gridImg.Visibility = Windows.UI.Xaml.Visibility.Visible; } else if (pack.Contains(StandardDataFormats.StorageItems)) { var storageItems = await pack.GetStorageItemsAsync(); StorageFile file = storageItems[0] as StorageFile; this.tbStorageItem.Text = file.Name; this.gridStorageItems.Visibility = Windows.UI.Xaml.Visibility.Visible; } } } private void btnCompleteShare_Click(object sender, RoutedEventArgs e) { this.shareOperation.ReportCompleted(); } } ~~~ 接着我们就要来调试这两个程序啦。只需要将接受共享数据的App按F5运行后关掉就好了,因为它会部署到本地的,或者也可以在Build选项卡中直接部署也是一样的。然后按F5运行共享数据的数据源App就好啦。 截图如下: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a034015d28e.jpg "") 这张图片我压缩过了,不如太大上传不了,所以可能看不清楚吧。下面是共享文本数据的过程截图啦。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a0340175af3.jpg "") ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a0340189fd5.jpg "") ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a034019d58e.jpg "") 这个是共享图像的截图,忘了说了,在前面的SpaceNeedle1.jpg就是下面这张图片我已经事先添加到工程里了的。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a03401b0ab2.jpg "") 紧接着我们共享这个docx文件,却发现在共享栏里没有了App49,发生了什么? ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a03401d702d.jpg "") ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a03401ec97e.jpg "") 下面这首刚才添加的受支持的文件类型,明显没有添加.docx,所以这也是一个需要注意的地方。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a034014a90b.jpg "") 而想知道如何算出10000的阶层可以看[“100的阶层真的算不出来吗?”](http://blog.csdn.net/nomasp/article/details/45484979) :[http://blog.csdn.net/nomasp/article/details/45484979](http://blog.csdn.net/nomasp/article/details/45484979)
';

Windows App开发之文件与数据

最后更新于:2022-04-01 16:23:42

# 读取文件和文件夹名 这一节开始我们将陆续看到Windows App是怎样操作文件的。 ### 在Windows上读取文件名、文件夹名 首先我们在XAML中定义一个Button和TextBlock,将读取文件/文件夹名的过程写在前者的click事件中,后者则用来显示文件信息。 ~~~ <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <StackPanel Orientation="Horizontal"> <Button Name="btnGetName" Width="200" Height="100" Content="读取文件名" Click="btnGetName_Click"/> <TextBlock Name="textBlockFileName" Width="500" Height="300" FontSize="30" Margin="12"/> </StackPanel> </Grid> ~~~ 以下这段代码,首先通过StorageFolder类读取图片库,然后使用异步的方式将图片库的文件和文件夹信息载入相应的List中。新建一个StringBuilder用以保存这些文件的信息,在这里只是使用了文件/文件夹的Name属性,但属性还有很多,比如Path属性。最后再将这些获取到的信息赋值给TextBlock即可。 ~~~ private async void btnGetName_Click(object sender, RoutedEventArgs e) { StorageFolder pictureFolder = KnownFolders.PicturesLibrary; IReadOnlyList<StorageFile> pictureFileList = await pictureFolder.GetFilesAsync(); IReadOnlyList<StorageFolder> pictureFolderList = await pictureFolder.GetFoldersAsync(); StringBuilder picutreFolderInfo = new StringBuilder(); foreach(StorageFile f in pictureFileList) { picutreFolderInfo.Append(f.Name+"\n"); } foreach(StorageFolder f in pictureFolderList) { picutreFolderInfo.Append(f.Name+"\n"); } textBlockFileName.Text = picutreFolderInfo.ToString(); } ~~~ 注意要在方法名前面加上async哦。还有要在清单文件中声明我们的应用要使用图片库哦,一会在Windows Phone中也一样。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fec9aad.jpg "") ### 在Windows Phone上读取文件名、文件夹名 后台代码不用做任何修改,只需把XAML代码修改修改以适应屏幕即可~ ~~~ <Grid> <StackPanel Orientation="Vertical"> <Button Name="btnGetName" Width="150" Height="70" HorizontalAlignment="Center" Content="读取文件名" Click="btnGetName_Click"/> <TextBlock Name="textBlockFileName" Width="300" Height="300" FontSize="30" Margin="12" TextWrapping="Wrap"/> </StackPanel> </Grid> ~~~ ### 读取文件名的其他方法 ~~~ private async void btnGetName_Click(object sender, RoutedEventArgs e) { StorageFolder picutureFolder = KnownFolders.PicturesLibrary; StringBuilder pictureFolderInfo = new StringBuilder(); IReadOnlyList<IStorageItem> pictureFileItem = await picutureFolder.GetItemsAsync(); foreach(var i in pictureFileItem) { if (i is StorageFolder) pictureFolderInfo.Append(i.Name + "\n"); else pictureFolderInfo.Append(i.Name + "\n"); } textBlockFileName.Text = pictureFolderInfo.ToString(); } ~~~ # 文件选取器 ### 使用文件选取器保存文件 就我个人而言,还是非常喜欢使用文件选取器的,因为能够用自己的代码来调用系统的各种弹框。 在这个示例中,首先在XAML中添加一个Button和一个TextBlock,分别命名为btnSaveFile和tBlockSaveInfo。对于这个保存文件这个操作在后台的Click事件中就可以轻易完成了。 ~~~ private async void btnSaveFile_Click(object sender, RoutedEventArgs e) { FileSavePicker saveFile = new FileSavePicker(); saveFile.SuggestedStartLocation = PickerLocationId.DocumentsLibrary; // 显示在下拉列表的文件类型 saveFile.FileTypeChoices.Add("批处理文件", new List<string>() { ".bat" }); // 默认的文件名 saveFile.SuggestedFileName = "SaveFile"; StorageFile file = await saveFile.PickSaveFileAsync(); if(file!=null) { // 在用户完成更改并调用CompleteUpdatesAsync之前,阻止对文件的更新 CachedFileManager.DeferUpdates(file); string fileContent = "@echo off \n dir/s \n pause"; await FileIO.WriteTextAsync(file, fileContent); // 当完成更改时,其他应用程序才可以对该文件进行更改。 FileUpdateStatus updateStatus = await CachedFileManager.CompleteUpdatesAsync(file); if(updateStatus==FileUpdateStatus.Complete) { tBlockSaveInfo.Text = file.Name + " 已经保存好了。"; } else { tBlockSaveInfo.Text = file.Name + " 保存失败了。"; } } else { tBlockSaveInfo.Text = "保存操作被取消。"; } } ~~~ 代码中的下拉列表的文件类型就是如下所示这个样子哟。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fee0675.jpg "") 大部分的内容我都已经通过注释的方式添加到代码中了,至于fileContent的那段代码到底是什么意思,大家试试就知道了,我感觉蛮有意思的。[3行代码列出硬盘上所有文件及文件夹](http://blog.csdn.net/nomasp/article/details/44927037) 如果大家试过打开这个bat文件,有没有觉得有趣呢? 更厉害的是,我们刚才所写的代码可以在Windows Phone上不经修改而直接使用。我的Lumia 638已经刷上了Windows 10预览版,大家可以瞧瞧,全新的资源管理器。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033ff003b1.jpg "") ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033ff1d06e.jpg "") ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033ff38f00.jpg "") ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033ff4f0d7.jpg "") ### 使用文件选取器打开文件 和用文件选取器保存文件相类似,打开文件的逻辑都差不多。这个示例中同样在XAML中定义一个名为btnOpenFile的Button和一个名为tBlockOpenInfo的TextBlock。 ~~~ private async void btnOpenFile_Click(object sender, RoutedEventArgs e) { FileOpenPicker openFile = new FileOpenPicker(); openFile.SuggestedStartLocation = PickerLocationId.DocumentsLibrary; openFile.ViewMode = PickerViewMode.List; openFile.FileTypeFilter.Add(".txt"); openFile.FileTypeFilter.Add(".docx"); openFile.FileTypeFilter.Add(".pptx"); // 选取单个文件 StorageFile file = await openFile.PickSingleFileAsync(); if (file != null) { tBlockOpenInfo.Text = "你所选择的文件是: " + file.Name; } else { tBlockOpenInfo.Text = "打开文件操作被取消。"; } // 选择多个文件 //IReadOnlyList<StorageFile> fileList = await openFile.PickMultipleFilesAsync(); //StringBuilder fileOpenInfo = new StringBuilder(); //if(fileList!=null) //{ // foreach( StorageFile f in fileList) // { // fileOpenInfo.Append(f.Name + "\n"); // } // tBlockOpenInfo.Text = "你所选择的文件是: "+"\n"+ fileOpenInfo.ToString(); //} //else //{ // tBlockOpenInfo.Text = "打开文件操作被取消。"; //} } ~~~ 我已经将选取多个文件的代码也列了出来,只需要取消注释即可。像ViewMode和FileTypeFilter这种属性,看看名字应该都知道了吧。重在实践。 在手机上也是通用的,刚才我试过了,成功进入了资源管理器,不过没能打开文件。应该是因为预览版的原因,这个预览版连Office都被移除了,估计会在下一版中添加通用版的Office应用。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033ff69349.jpg "") # 写入和读取 ### 准备工作 在XAML中添加一个TextBlock用于显示相关信息,添加一个Button来使用它的Click事件,当然了,最后分别创建2个。 ### 创建文件和读取文件 1.实例化StorageFolder类 我们的文件不可能让其随意保存在计算机/手机中的任何一个地方,应该先确定它的文件夹,对吧? 在新的Windows 8中,微软开启了Windows上的App时代,下载的软件再也不能随意安装到任何地方了,而是由操作系统统一放到一块叫做“独立存储”的地方。这也是出于安全的考虑。用过Windows Phone 8的朋友应该更加清楚了。 那么下面这行代码的LocalFolder究竟在哪里呢? ~~~ StorageFolder folder = Windows.Storage.ApplicationData.Current.LocalFolder; ~~~ 下图中的文件,就是我当前所写的App。(补充一条哦,一开始我装了Win8后,下载了一个游戏,模拟类的,有金币呀什么的,后来我找到这个App的文件,将数据改了之后金币就哗哗的啦。当然了,对于其他单机而言这个完全不值一提,但App的数据,相信还有很多人没有改过吧。) ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033ff865f9.jpg "") 那么这张图中的红方框的文件夹就是LocalFolder啦,下面还有一个存储漫游文件的文件夹。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033ffa3ab8.jpg "") 不论是读取文件还是写入文件,都得先确定一个文件夹哦。 2.实例化StorageFile 确定了文件夹,就得确定文件咯。对于创建文件而言,执行以下代码。既然用到了异步,在函数上加上async是必不可少的咯,这一点我们在前面讲到过。后面的ReplaceExisting属性是指的,如果该文件(名)已经存在了,则替换它。 ~~~ StorageFile file = await folder.CreateFileAsync("New Document.txt", CreationCollisionOption.ReplaceExisting); ~~~ 那么对于读取文件呢,就直接读取好啦。 ~~~ StorageFile file = await folder.GetFileAsync("sample.txt"); ~~~ 3.创建和读取文件 将文本写入文件按照如下代码,将文件名和文本内容(字符串)。 ~~~ await FileIO.WriteTextAsync(file, "Write text to file."); ~~~ 读取文件也是类似的。 ~~~ string text = await FileIO.ReadTextAsync(file); ~~~ 我们还可以将这个读取的字符串传递给前面定义的TextBlock来加以调试。以下是完整的代码。 ~~~ // 创建文件 StorageFolder folder = Windows.Storage.ApplicationData.Current.LocalFolder; StorageFile file = await folder.CreateFileAsync("New Document.txt", CreationCollisionOption.ReplaceExisting); await FileIO.WriteTextAsync(file, "Write text to file."); ~~~ ~~~ // 2 从文本读取文件 StorageFolder folder = Windows.Storage.ApplicationData.Current.LocalFolder; StorageFile file = await folder.GetFileAsync("sample.txt"); string text = await Windows.Storage.FileIO.ReadTextAsync(file); tBlockReadInfo.Text = text; ~~~ ### 使用缓冲区将字节写入到文件或从文件读取字节 1.实例化StorageFolder类 同上。 2.实例化StorageFile 同上。 3.将字节写入到文件 a.建立缓冲区 ~~~ var buffer = Windows.Security.Cryptography.CryptographicBuffer.ConvertStringToBinary("There's buffer ...... ", Windows.Security.Cryptography.BinaryStringEncoding.Utf8); ~~~ b.将缓冲区中的字节写入到文件 ~~~ await Windows.Storage.FileIO.WriteBufferAsync(file, buffer); ~~~ 4.从文件读取字节 a.将文件加载到缓冲区 ~~~ var buffer = await Windows.Storage.FileIO.ReadBufferAsync(file); ~~~ b.实例化DataReader,读取缓冲区 ~~~ DataReader dataReader = Windows.Storage.Streams.DataReader.FromBuffer(buffer); ~~~ c.从DataReader对象中读取字符串 ~~~ string text = dataReader.ReadString(buffer.Length); ~~~ ### 使用流将文本写入文件或从文件读取文本 1.实例化StorageFolder类 同上。 2.实例化StorageFile 同上。 3.新建流,并异步地将file打开,使用可读写的方式 ~~~ var stream = await file.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite); ~~~ 4.将文本写入到文件 a.使用using ~~~ using (var writeStream= stream.GetOutputStreamAt(0)) { ...... } ~~~ b.(在using语句的花括号内)创建DataWriter对象,并调用DataWriter.WriteString方法,将文本写入到writeStream中 ~~~ DataWriter dataWriter = new DataWriter(writeStream); dataWriter.WriteString("Stream is a good thing."); ~~~ c.将文本保存到文件中,并通过StoreAsync和FlushAsync方法存储和关闭流 ~~~ await dataWriter.StoreAsync(); await writeStream.FlushAsync(); ~~~ 5.从文件读取文本 a.获取该流的size ~~~ var size = stream.Size; ~~~ b.使用using ~~~ using (var readStream = stream.GetOutputStreamAt(0)) { ...... } ~~~ c.(在using语句的花括号内)创建DataWriter对象,并调用LoadAsync方法,最后调用ReadString即可。最后还可以将信息输出到TextBlock中。 ~~~ DataReader dataReader = new DataReader(readStream); uint uintBytes = await dataReader.LoadAsync((uint)size); string text = dataReader.ReadString(uintBytes); tBlockReadInfo.Text = text; ~~~ # 获取文件属性 这一节来看看获取文件属性吧,可以获取到文件名、类型、最近访问时间等等属性。 ### 创建Button和TextBlock 下面这段代码呢,都很简单。 ~~~ <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center"> <Button Width="200" Height="70" Name="btnGetProp" Content="获取文件属性" Click="btnGetProp_Click"/> <TextBlock Name="tBlockProp" Margin="12" Width="480" FontSize="30"/> </StackPanel> </Grid> </Page> ~~~ 在Click事件中,先获取到图片库,当然了,你可以获取其他地方,我电脑上的库文件中,就只有文档库和图片库有文件了。然后创建一个文件查询,最后将这些文件都赋给files。这里的var可谓是非常的强大啊。实例化一个StringBuilder对象来辅助输出信息那真是太完美不过了,我们在前面也常用到它。 ~~~ var folder = KnownFolders.PicturesLibrary; var fileQuery = folder.CreateFileQuery(); var files = await fileQuery.GetFilesAsync(); StringBuilder fileProperties = new StringBuilder(); for (int i = 0; i < files.Count; i++) { StorageFile file = files[i]; fileProperties.AppendLine("File name: " + file.Name); fileProperties.AppendLine("File type: " + file.FileType); BasicProperties basicProperties = await file.GetBasicPropertiesAsync(); string fileSize = string.Format("{0:n0}", basicProperties.Size); fileProperties.AppendLine("File size: " + fileSize + " bytes"); fileProperties.AppendLine("Date modified: " + basicProperties.DateModified); fileProperties.AppendLine(" "); } tBlockProp.Text = fileProperties.ToString(); ~~~ 这样一来就完成对Name、FileType、Size和DateModified属性的获取,但还有一类属性,则比较难以获取,它们就是“扩展属性”。 ~~~ List<string> propertiesName = new List<string>(); propertiesName.Add("System.DateAccessed"); propertiesName.Add("System.FileOwner"); IDictionary<string, object> extraProperties = await file.Properties.RetrievePropertiesAsync(propertiesName); var propValue = extraProperties[dateAccessedProperty]; if (propValue != null) fileProperties.AppendLine("Date accessed: " + propValue); propValue = extraProperties[fileOwnerProperty]; if (propValue != null) fileProperties.AppendLine("File owner: " + propValue); ~~~ 最后将fileProperties传递给TextBlock即可。 ~~~ tBlockProp.Text = fileProperties.ToString(); ~~~ 最后调试App,就会像下图一样了。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033ffb96c2.jpg "") 但是如你所见,文件名太长了却无法自动换行,而且数据也显示不完整。改改XAML中的TextBlock即可。TextWrapping属性设置为Wrap,则可以换行;将TextBlock添加到ScrollViewer内则会显示出一个滚动条。 ~~~ <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center"> <Button Width="200" Height="70" Name="btnGetProp" Content="获取文件属性" Click="btnGetProp_Click"/> <ScrollViewer> <TextBlock Name="tBlockProp" Margin="12" Width="480" FontSize="30" TextWrapping="Wrap"/> </ScrollViewer> </StackPanel> </Grid> ~~~ ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033ffd7c17.jpg "") # 保存、读取、删除应用数据 在前面的几节中,都是关于数据的,这方面的内容其实还有很多很多,省略掉一部分后,也还是有很多。这一节是很重要的一部分,它关于如何保存和读取数据。 先来看看一个大概的背景吧,我这里写的很简单啦。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a03400005b6.jpg "") 保存的内容就是这四个框框里填写的数据咯。先上XAML代码。 ~~~ <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center"> <StackPanel Orientation="Vertical"> <Rectangle Name="recRed" Width="200" Height="200" Fill="Red"/> <Rectangle Name="recGreen" Width="100" Height="400" Fill="Green"/> </StackPanel> <StackPanel Orientation="Vertical"> <StackPanel Orientation="Horizontal"> <StackPanel Orientation="Vertical"> <TextBlock Text="红色矩形的长" FontSize="25" Margin="12" Width="150" Height="50" /> <TextBlock Text="红色矩形的宽" FontSize="25" Margin="12" Width="150" Height="50" /> <TextBlock Text="绿色矩形的长" FontSize="25" Margin="12" Width="150" Height="50" /> <TextBlock Text="绿色矩形的宽" FontSize="25" Margin="12" Width="150" Height="50" /> </StackPanel> <StackPanel Orientation="Vertical"> <TextBox Name="tBoxRedHeight" FontSize="25" Width="150" Height="50" Margin="12"/> <TextBox Name="tBoxRedWidth" FontSize="25" Width="150" Height="50" Margin="12"/> <TextBox Name="tBoxGreenHeight" FontSize="25" Width="150" Height="50" Margin="12" /> <TextBox Name="tBoxGreenWidth" FontSize="25" Width="150" Height="50" Margin="12" /> </StackPanel> </StackPanel> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center"> <Button Width="150" Height="70" Name="btnSaveAppData" Click="btnSaveAppData_Click" Content="保存应用数据"/> <Button Width="150" Height="70" Name="btnReadAppData" Click="btnReadAppData_Click" Content="读取应用数据"/> </StackPanel> </StackPanel> </StackPanel> </Grid> ~~~ ### 单个设置 先来看看单个设置呗,下面就是代码咯。 ~~~ Windows.Storage.ApplicationDataContainer localSettings = Windows.Storage.ApplicationData.Current.LocalSettings; Windows.Storage.StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder; public MainPage() { this.InitializeComponent(); } private void btnSaveAppData_Click(object sender, RoutedEventArgs e) { localSettings.Values["RectangleRedHeight"] = tBoxRedHeight.Text; localSettings.Values["RectangleRedWidth"] = tBoxRedWidth.Text; localSettings.Values["RectangleGreenHeight"] = tBoxGreenHeight.Text; localSettings.Values["RectangleGreenWidth"] = tBoxGreenWidth.Text; } private void btnReadAppData_Click(object sender, RoutedEventArgs e) { Object objRectangleRedHeight = localSettings.Values["RectangleRedHeight"]; Object objRectangleRedWidth = localSettings.Values["RectangleRedWidth"]; Object objRectangleGreenHeight = localSettings.Values["RectangleGreenHeight"]; Object objRectangleGreenWidth = localSettings.Values["RectangleGreenWidth"]; recRed.Height = double.Parse(objRectangleRedHeight.ToString()); recRed.Width = double.Parse(objRectangleRedWidth.ToString()); recGreen.Height = double.Parse(objRectangleGreenHeight.ToString()); recGreen.Width = double.Parse(objRectangleGreenWidth.ToString()); } ~~~ 首先定义了两个全局变量,如果看过前面几篇文章,这个应该就非常清楚了。顾名思义,第一个是用来保存本地设置的,第二个则是用来访问本地文件夹的。这里是单个设置地进行保存的,后面还有2种方式。那么就来调试吧,注意在点击了保存数据按钮之后把App关掉哦,关掉之后再加载,这样才算是保存了应用数据嘛,你说对不对呢? 以下就是我的测试结果了。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a03400139c5.jpg "") ### 复合设置 我们的设计都不用变,后台代码修改如下。 ~~~ Windows.Storage.ApplicationDataContainer localSettings = Windows.Storage.ApplicationData.Current.LocalSettings; Windows.Storage.StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder; public MainPage() { this.InitializeComponent(); } private void btnSaveAppData_Click(object sender, RoutedEventArgs e) { Windows.Storage.ApplicationDataCompositeValue compositeSettings = new ApplicationDataCompositeValue(); compositeSettings["RectangleRedHeight"] = tBoxRedHeight.Text; compositeSettings["RectangleRedWidth"] = tBoxRedWidth.Text; compositeSettings["RectangleGreenHeight"] = tBoxGreenHeight.Text; compositeSettings["RectangleGreenWidth"] = tBoxGreenWidth.Text; localSettings.Values["RectangleSettings"] = compositeSettings; } private async void btnReadAppData_Click(object sender, RoutedEventArgs e) { Windows.Storage.ApplicationDataCompositeValue compositeSettings = (Windows.Storage.ApplicationDataCompositeValue)localSettings.Values["RectangleSettings"]; if (compositeSettings == null) { Windows.UI.Popups.MessageDialog messageDialog = new Windows.UI.Popups.MessageDialog("你好像没有保存任何应用数据哦!"); await messageDialog.ShowAsync(); } else { recRed.Height = double.Parse(compositeSettings["RectangleRedHeight"].ToString()); recRed.Width = double.Parse(compositeSettings["RectangleRedWidth"].ToString()); recGreen.Height = double.Parse(compositeSettings["RectangleGreenHeight"].ToString()); recGreen.Width = double.Parse(compositeSettings["RectangleGreenWidth"].ToString()); } } ~~~ 使用ApplicationDataCompositeValue 会创建一个复合设置,通过代码所示方式即可添加数据,最后会将其添加到localSettings中。 读取数据的时候,同样是先在localSettings中通过键值对的方式来取出这个复合设置。如果该设置为空,就会调用MessageDialog控件弹窗通知没有保存数据。对于这个控件,可以访问这里:[【万里征程——Windows App开发】控件大集合2](http://blog.csdn.net/nomasp/article/details/44781145)。如果复合设置存在则将他们分别进行类型转换后复制给相应的矩形的属性。 ### 在容器中存放数据 在容器存放数据其实也就这么回事啦,无非就是先创建一个容器,然后如果创建成功了,就在其中添加相应的数据即可。 至于加载数据,在这里我使用了一个bool变量来检查容器是不是已经创建好了,如果创建好了就可以将相应的数据取出然后赋值了,如果没有的话则一样挑出弹窗。 ~~~ Windows.Storage.ApplicationDataContainer localSettings = Windows.Storage.ApplicationData.Current.LocalSettings; Windows.Storage.StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder; public MainPage() { this.InitializeComponent(); } private void btnSaveAppData_Click(object sender, RoutedEventArgs e) { Windows.Storage.ApplicationDataContainer containerSettings = localSettings.CreateContainer("RecSettingsContainer", Windows.Storage.ApplicationDataCreateDisposition.Always); if (localSettings.Containers.ContainsKey("RecSettingsContainer")) { localSettings.Containers["RecSettingsContainer"].Values["RectangleRedHeight"] = tBoxRedHeight.Text; localSettings.Containers["RecSettingsContainer"].Values["RectangleRedWidth"] = tBoxRedWidth.Text; localSettings.Containers["RecSettingsContainer"].Values["RectangleGreenHeight"] = tBoxGreenHeight.Text; localSettings.Containers["RecSettingsContainer"].Values["RectangleGreenWidth"] = tBoxGreenWidth.Text; } } private async void btnReadAppData_Click(object sender, RoutedEventArgs e) { bool hasContainerSettings = localSettings.Containers.ContainsKey("RecSettingsContainer"); if(hasContainerSettings) { recRed.Height = double.Parse(localSettings.Containers["RecSettingsContainer"].Values["RectangleRedHeight"].ToString()); recRed.Width = double.Parse(localSettings.Containers["RecSettingsContainer"].Values["RectangleRedWidth"].ToString()); recGreen.Height = double.Parse(localSettings.Containers["RecSettingsContainer"].Values["RectangleGreenHeight"].ToString()); recGreen.Width = double.Parse(localSettings.Containers["RecSettingsContainer"].Values["RectangleGreenWidth"].ToString()); } else { Windows.UI.Popups.MessageDialog messageDialog = new Windows.UI.Popups.MessageDialog("你好像没有保存任何应用数据哦!"); await messageDialog.ShowAsync(); } } ~~~ 接下来就来个运行的截图咯,还有弹框的截图^_^ ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a0340029762.jpg "") ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a034003bfe7.jpg "") ### 删除数据 1.对于单个设置和复合设置 ~~~ localSettings.Values.Remove("compositeSettings"); ~~~ 2.对于复合数据 ~~~ localSettings.DeleteContainer("containerSettings"); ~~~ 删除并不难,或者说,这一节都不难。有了这些,我们在做游戏的时候,就可以将用户对游戏的设置都保存下来啦。
';

Windows App开发之集合控件与数据绑定

最后更新于:2022-04-01 16:23:40

# 为ListView和GridView添加数据 ListView采用垂直堆叠得方式显示数据,而GridView则采用水平堆叠得方式。 长相的话嘛,它们都差不多。 ~~~ <Grid Name="grid1" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <ListView x:Name="listView1" SelectionChanged="listView1_SelectionChanged"> <x:String>Item 1</x:String> <x:String>Item 2</x:String> </ListView> <GridView x:Name="gridView1" SelectionChanged="gridView1_SelectionChanged"> <x:String>Item 1</x:String> <x:String>Item 2</x:String> </GridView> </Grid> ~~~ 当然,也可以在后台代码上添加。我只是为了将它们放在一起比较而已,这些代码堆一起肯定是很丑的。 ~~~ ListView listView1 = new ListView(); listView1.Items.Add("Item 1"); listView1.Items.Add("Item 2"); listView1.Items.Add("Item 3"); listView1.SelectionChanged += listView1_SelectionChanged; grid1.Children.Add(listView1); GridView gridView1 = new GridView(); gridView1.Items.Add("Item 1"); gridView1.Items.Add("Item 2"); gridView1.SelectionChanged += gridView1_SelectionChanged; grid1.Children.Add(gridView1); ~~~ 如果只是像上面这样来添加内容会不会比较麻烦呢,我们也可以把这些Item 1、Item 2之类的全部放在List中。 ~~~ List<String> itemsList = new List<string>(); itemsList.Add("Item 1"); itemsList.Add("Item 2"); ListView listView1 = new ListView(); listView1.ItemsSource = itemsList; listView1.SelectionChanged += listView1_SelectionChanged; grid1.Children.Add(listView1); ~~~ 这样一来所显示的ListView就是两行,非常简陋,完全不能够满足要求。那么我们可以用它的ItemTemplate属性来再里面添加一些东西,如下所示,我们可以在Grid中写一个Image绑定头像,用TextBlock绑定用户的ID,再来一个TextBlock绑定用户的消息,还可以来写边框呀什么的。而这些乱七八糟的Binding之类的,以后我们也会一起讲的哦,现在只要它们是数据绑定就好。 ~~~ <Page.Resources> <CollectionViewSource x:Name="collectionVS" Source="{Binding Items}"/> </Page.Resources> <Grid Name="grid1" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <ListView x:Name="listView1" ItemsSource="{Binding Source={StaticResource collectionVS}}" SelectionChanged="listView1_SelectionChanged"> <ListView.ItemTemplate> <DataTemplate> <Grid> </Grid> </DataTemplate> </ListView.ItemTemplate> </ListView> </Grid> ~~~ 还可以像下面这样哦,通过WrapGrid来决定这些Item的摆放方式。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fd4fef5.jpg "") ~~~ <Grid Name="grid1" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <ListView VerticalAlignment="Bottom"> <ListView.ItemsPanel> <ItemsPanelTemplate> <WrapGrid Orientation="Vertical" MaximumRowsOrColumns="2"/> </ItemsPanelTemplate> </ListView.ItemsPanel> <Rectangle Height="100" Width="100" Fill="Wheat" /> <Rectangle Height="100" Width="100" Fill="White" /> <Rectangle Height="100" Width="100" Fill="Gainsboro" /> <Rectangle Height="100" Width="100" Fill="Violet" /> <Rectangle Height="100" Width="100" Fill="DarkBlue" /> <Rectangle Height="100" Width="100" Fill="RosyBrown" /> <Rectangle Height="100" Width="100" Fill="SaddleBrown" /> <Rectangle Height="100" Width="100" Fill="AliceBlue" /> <Rectangle Height="100" Width="100" Fill="Fuchsia" /> <Rectangle Height="100" Width="100" Fill="Aqua" /> <Rectangle Height="100" Width="100" Fill="Tan" /> </ListView> </Grid> ~~~ 当然啦,对于ListView和GridView而言,知道用户选择了哪一项是很重要的。SelectionMode属性决定了ListView和GridView的选择模式:单个、多个、无、扩展。 下面这个函数将选择的项给了selectedItems啦。我们还可以通过IsItemClickEnabled来启用ListView和GridView的点击事件,但是务必要注意将SelectionMode设置为None。 ~~~ private void listView1_SelectionChanged(object sender, SelectionChangedEventArgs e) { selectedItems = (List<object>)e.AddedItems; } ~~~ # 为ListView和GridViewt添加分组 本文承接“为ListView和GridView添加数据”。 在上一节中我们已经了解了怎样将数据绑定到ListView或GridView,但既然要用到这两个控件往往是因为数据繁多,那么几乎就不可避免的要让其能够分组。我们所绑定的数据源可能是项列表,其中的每个项甚至还有其自己的项,那么问题就来了。 一时不会也想不出什么宏伟的例子,就做一个简单的闹钟的时间表的ListView和GridView吧。那么先在项目中添加一个类,最好在Shared下。内容都是很简易的,闹钟的标题、时间、备注等,为了增加一级目录就加了一个AlarmMode,就算作学习和生活吧,学习生活两不误…… ~~~ public class Alarm { public string Title { get; set; } public DateTime AlarmClockTime { get; set; } public string Description { get; set; } public string AlarmMode { get; set; } } ~~~ ~~~ public class AlarmMode { public AlarmMode() { alarmMode = new ObservableCollection<Alarm>(); } public string Name { get; set; } public ObservableCollection<Alarm> alarmMode { get; private set; } } ~~~ 首先,先来定义一个全局的时间,然后在页面加载时加载两个函数(将在下一步定义)。 ~~~ DateTime globalTime; ~~~ ~~~ protected override void OnNavigatedTo(NavigationEventArgs e) { DateTime.TryParse("1/1/2115", out globalTime); AddAlarm(); AddAlarmMode(); } ~~~ 一大波数据正在靠近! ~~~ private void AddAlarm() { List<Alarm> listAlarm = new List<Alarm>(); listAlarm.Add(new Alarm() { Title = "Alarm1", Description = "First Alarm", AlarmClockTime = globalTime.AddHours(1), AlarmMode = "Alarm In Life" }); listAlarm.Add(new Alarm() { Title = "Alarm2", Description = "Second Alarm", AlarmClockTime = globalTime.AddHours(11), AlarmMode = "Alarm In Life" }); listAlarm.Add(new Alarm() { Title = "Alarm3", Description = "Third Alarm", AlarmClockTime = globalTime.AddDays(1), AlarmMode = "Alarm In Life" }); listAlarm.Add(new Alarm() { Title = "Alarm1", Description = "First Alarm", AlarmClockTime = globalTime.AddHours(12), AlarmMode = "Alarm In Study" }); listAlarm.Add(new Alarm() { Title = "Alarm2", Description = "Second Alarm", AlarmClockTime = globalTime.AddHours(15), AlarmMode = "Alarm In Study" }); listAlarm.Add(new Alarm() { Title = "Alarm3", Description = "Third Alarm", AlarmClockTime = globalTime.AddMonths(1), AlarmMode = "Alarm In Study" }); ar alarmSetting = from ala in listAlarm group ala by ala.AlarmMode into alaSetting orderby alaSetting.Key select alaSetting; collectionVSAlarm.Source = alarmSetting; } private void AddAlarmMode() { List<AlarmMode> listAlarmMode = new List<AlarmMode>(); AlarmMode am1 = new AlarmMode(); am1.Name = "Alarm In Life"; am1.alarmMode.Add(new Alarm() { Title = "Alarm1", Description = "First Alarm", AlarmClockTime = globalTime.AddHours(1), }); am1.alarmMode.Add(new Alarm() { Title = "Alarm2", Description = "Second Alarm", AlarmClockTime = globalTime.AddHours(11), }); am1.alarmMode.Add(new Alarm() { Title = "Alarm3", Description = "Third Alarm", AlarmClockTime = globalTime.AddDays(1), }); listAlarmMode.Add(am1); AlarmMode am2 = new AlarmMode(); am2.Name = "Alarm In Study"; am2.alarmMode.Add(new Alarm() { Title = "Alarm1", Description = "First Alarm", AlarmClockTime = globalTime.AddHours(12), }); am2.alarmMode.Add(new Alarm() { Title = "Alarm2", Description = "Second Alarm", AlarmClockTime = globalTime.AddHours(15), }); am2.alarmMode.Add(new Alarm() { Title = "Alarm3", Description = "Third Alarm", AlarmClockTime = globalTime.AddMonths(1), }); listAlarmMode.Add(am2); collectionVSAlarmMode.Source = listAlarmMode; } ~~~ 这些数据都是乱七八糟啦,大家凑合着看。这是两个函数,数据我都是用List<>来定义的,将数据通过Add函数添加到listAlarm和listAlarmMode中即可。最后再从listAlarm中根据AlarmMode挑出数据到alaSetting,同时还要根据Key值进行排序最后选出并连接到collectionVSAlarm的Source属性中。这个是需要在MainPage.xaml中定义的哦,就像 ~~~ <UserControl.Resources> <CollectionViewSource x:Name="collectionVSAlarm" IsSourceGrouped="True"/> <CollectionViewSource x:Name="collectionVSAlarmMode" IsSourceGrouped="True" ItemsPath="alarmMode"/> </UserControl.Resources> ~~~ 然后我们还需要创建一个ListGridGroupStyle类来继承GroupStyleSelector,重载它的SelectGroupStyleCore方法,并且返回ListGridGroupStyleResource资源,这个资源在博客后文中有定义,其定义在App.xaml中。相应的代码如下咯: ~~~ public class ListGridGroupStyle : GroupStyleSelector { protected override GroupStyle SelectGroupStyleCore(object group, uint level) { return (GroupStyle)App.Current.Resources["ListGridGroupStyleResource"]; } } ~~~ 方法重载好之后就需要在前面的UserControl.Resources中加上以下这条代码啦。 ~~~ <local:ListGridGroupStyle x:Key="ListGridGroupStyleResource"/> ~~~ 然后我们来一系列的基本样式到App.xaml中就好啦,关于资源文件的使用我们在后面会系统的来学习。这里的DataTemplate和GroupStyle都在资源字典中,前者是Template模板,后者是Style风格。内容的排版大家都随意啦,记得设置好Key值。 ~~~ <Application.Resources> <ResourceDictionary> <DataTemplate x:Key="dataTemplateListView"> <StackPanel Width="700" Margin="10"> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Title}" FontWeight="Bold" Margin="12"/> <TextBlock Text="{Binding AlarmClockTime}" TextWrapping="NoWrap" Margin="12"/> <TextBlock Text="{Binding Description}" TextWrapping="NoWrap" Margin="12"/> </StackPanel> </StackPanel> </DataTemplate> <GroupStyle x:Key="ListGridGroupStyleResource"> <GroupStyle.HeaderTemplate> <DataTemplate> <Grid Background="LightGray" > <TextBlock Text='{Binding Key}' Foreground="CornflowerBlue" Margin="12" /> </Grid> </DataTemplate> </GroupStyle.HeaderTemplate> </GroupStyle> </ResourceDictionary> </Application.Resources> ~~~ 那么这些各种资源都定义好了之后就在MainPage.xaml把下面这些敲进去。各种资源的调用在这里尤其需要注意,其实对于稍微复杂一丁点的程序而言,名称就已经变得让人崩溃了。所以拥有一个良好的命名习惯很重要。 ~~~ <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid.ColumnDefinitions> <ColumnDefinition Width="auto"/> <ColumnDefinition Width="auto"/> </Grid.ColumnDefinitions> <GridView Grid.Column="0" ItemsSource="{Binding Source={StaticResource collectionVSAlarmMode}}" Margin="12,120,12,12" MaxHeight="600" > <GridView.ItemTemplate> <DataTemplate> <StackPanel Margin="18"> <TextBlock Text="{Binding Title}" FontWeight="ExtraBold" /> <TextBlock Text="{Binding AlarmClockTime}" FontWeight="Light" TextWrapping="NoWrap" /> <TextBlock Text="{Binding Description}" TextWrapping="NoWrap" /> </StackPanel> </DataTemplate> </GridView.ItemTemplate> <GridView.ItemsPanel> <ItemsPanelTemplate> <ItemsWrapGrid MaximumRowsOrColumns="2"/> </ItemsPanelTemplate> </GridView.ItemsPanel> <GridView.GroupStyle> <GroupStyle> <GroupStyle.HeaderTemplate> <DataTemplate> <Grid Background="Green" Margin="12"> <TextBlock Text='{Binding Name}' Foreground="Bisque" Margin="36"/> </Grid> </DataTemplate> </GroupStyle.HeaderTemplate> </GroupStyle> </GridView.GroupStyle> </GridView> <ListView Grid.Column="1" ItemsSource="{Binding Source={StaticResource collectionVSAlarm}}" ItemTemplate="{StaticResource dataTemplateListView}" GroupStyleSelector="{StaticResource ListGridGroupStyleResource}" Margin="120" /> </Grid> ~~~ ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fd6391f.jpg "") 我这写的真是太丑了哎,做产品的时候可得好好调调了。 # 缩放视图SemanticZoom 相信用过Windows Phone或者Windows 8/8.1/10的朋友对下面这张截图肯定不陌生。这就是通过SemanticZoom来实现的,当数据过多时,这种控件尤其适用。它有一个放大视图ZoomedInView和一个缩小试图ZoomedOutView,前者主要用来显示当前页面的详细信息,后者则致力于快速导航。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fd7d1f4.jpg "") 那么我就自己来动手实践咯,首先我们在XAML中添加大致的界面,就像画画要先画轮廓一样。 ~~~ <Grid Name="grid1" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <SemanticZoom x:Name="semanticZoom" VerticalAlignment="Center" HorizontalAlignment="Center"> <SemanticZoom.ZoomedOutView> </SemanticZoom.ZoomedOutView> <SemanticZoom.ZoomedInView> </SemanticZoom.ZoomedInView> </SemanticZoom> </Grid> ~~~ 然后分别在这两个视图中添加你想要加入的东西。这里的核心就是,ZoomedOutView和ZoomedInView都是使用的同一个CollectionViewSource对象作为自己的数据集的。而这个属性我们在“为ListView和GridView分组”谈到过。 我们先把后台代码写好,我就像一篇那样装模作样写一个类吧。 ~~~ public class Alarm { public string Title { get; set; } public DateTime AlarmClockTime { get; set; } public string Description { get; set; } } ~~~ 然后用一个函数来添加一大堆数据……一大堆数据。 ~~~ private Alarm[] AddAlarmData() { return new Alarm[] { new Alarm {Title="Alarm 1",AlarmClockTime=globalTime.AddHours(17),Description="First Alarm for Study" }, new Alarm {Title="Alarm 2",AlarmClockTime=globalTime.AddHours(2),Description="Second Alarm for Study" }, new Alarm {Title="Alarm 3",AlarmClockTime=globalTime.AddHours(7),Description="Third Alarm for Study" }, new Alarm {Title="Alarm 4",AlarmClockTime=globalTime.AddHours(4),Description="4th Alarm for Study" }, new Alarm {Title="Alarm 5",AlarmClockTime=globalTime.AddHours(5),Description="First Alarm for Fun" }, new Alarm {Title="Alarm 6",AlarmClockTime=globalTime.AddHours(1),Description="First Alarm for Fun" }, new Alarm {Title="Alarm 7",AlarmClockTime=globalTime.AddHours(15),Description="Second Alarm for Fun" }, new Alarm {Title="Alarm 8",AlarmClockTime=globalTime.AddHours(9),Description="Third Alarm for Fun" }, new Alarm {Title="Alarm 9",AlarmClockTime=globalTime.AddHours(20),Description="4th Alarm for Fun" }, new Alarm {Title="Alarm 10",AlarmClockTime=globalTime.AddHours(14),Description="Second Alarm for Sleep" }, new Alarm {Title="Alarm 11",AlarmClockTime=globalTime.AddHours(9),Description="First Alarm for Sleep" } }; } ~~~ 因为我们最后要把放大视图变成缩小视图,记得缩小视图上面有一些ABCD之类的字母么,这里我们用的是时间,就分成中午晚上等好啦。就通过下面这样的一个函数来搞定。其用了一个键值对,用time作为参数。后面再将这些数据筛选出来,绑定到新添加的CollectionViewSource中。至于gridView1和gridView2是即将添加到XAML中,这里可以先不填,一回再补上。 ~~~ Func<int, string> SwitchTime = (time) => { if (time <= 10 && time >= 6) return "上午"; else if (time > 10 && time < 14) return "中午"; else if (time >= 14 && time <= 20) return "下午"; else return "晚上"; }; var varTime = from t in AddAlarmData() orderby t.AlarmClockTime.Hour group t by SwitchTime(t.AlarmClockTime.Hour); CollectionViewSource collectionVS = new CollectionViewSource(); collectionVS.IsSourceGrouped = true; collectionVS.Source = varTime; this.gridView1.ItemsSource = collectionVS.View.CollectionGroups; this.gridView2.ItemsSource = collectionVS.View; ~~~ 我们先来写主视图(也就是放大视图)。 ~~~ <GridView x:Name="gridView2" IsSwipeEnabled="True" HorizontalAlignment="Center" VerticalAlignment="Center" ScrollViewer.IsHorizontalScrollChainingEnabled="False" Width="1800" Height="1000"> <GridView.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" Margin="12" HorizontalAlignment="Left" Background="White"> <TextBlock Text="{Binding Title}" TextWrapping="Wrap" Foreground="Red" FontFamily="Harrington" Width="150" Height="100" FontSize="26" FontWeight="Light"/> <TextBlock Text="{Binding AlarmClockTime}" Foreground="Red" TextWrapping="Wrap" Width="150" Height="100" FontFamily="Harrington" FontSize="26" FontWeight="Light"/> <TextBlock Text="{Binding Description}" Foreground="Red" TextWrapping="Wrap" Width="150" Height="100" FontFamily="Harrington" FontSize="26" FontWeight="Light"/> </StackPanel> </DataTemplate> </GridView.ItemTemplate> <GridView.ItemsPanel> <ItemsPanelTemplate> <ItemsWrapGrid MaximumRowsOrColumns="8"/> </ItemsPanelTemplate> </GridView.ItemsPanel> <GridView.GroupStyle> <GroupStyle> <GroupStyle.HeaderTemplate> <DataTemplate> <TextBlock Text='{Binding Key}' Foreground="{StaticResource ApplicationForegroundThemeBrush}" Margin="12" FontSize="30" FontFamily="华文彩云" FontWeight="ExtraBold" /> </DataTemplate> </GroupStyle.HeaderTemplate> </GroupStyle> </GridView.GroupStyle> </GridView> ~~~ 相信大家都能看得懂,另外稍后我会在截图中添加一些注释的哦。然后是缩小视图。 ~~~ <GridView Name="gridView1" Background="Wheat" ScrollViewer.IsHorizontalScrollChainingEnabled="False" HorizontalAlignment="Center" VerticalAlignment="Center" Width="600" Height="200"> <GridView.ItemTemplate> <DataTemplate> <TextBlock Width="100" Height="100" Text="{Binding Group.Key}" FontFamily="华文行楷" FontWeight="Normal" FontSize="24" /> </DataTemplate> </GridView.ItemTemplate> <GridView.ItemsPanel> <ItemsPanelTemplate> <ItemsWrapGrid ItemWidth="100" ItemHeight="100" MaximumRowsOrColumns="2"/> </ItemsPanelTemplate> </GridView.ItemsPanel> <GridView.ItemContainerStyle> <Style TargetType="GridViewItem"> <Setter Property="Margin" Value="12" /> <Setter Property="Padding" Value="3" /> <Setter Property="BorderThickness" Value="1" /> <Setter Property="Background" Value="Green"/> </Style> </GridView.ItemContainerStyle> </GridView> ~~~ 那么代码就到这里为止了,接下来自然就是截图了。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fd94d93.jpg "") ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fda8bc8.jpg "") (这种图片如果看不清的话可以保存到电脑上再看。) 想了解字体相关的信息,可以看第九章的“使用更多字体”。 # 数据绑定介绍 ### 简单的数据绑定示例 相比于理论,我更倾向于从实践中开始博客,尤其是对于数据绑定。那么,我们先来看看几个简单的例子。 1.数据绑定到TextBox 我们依旧使用前面的闹钟类来开始。在下面的代码中,我们有属性、构造函数,还有一个ToString()方法的重载。之所以重载这个方法是因为我们想在最后绑定的时候,这三个属性能够在TextBox上显示得更加工整。 ~~~ public class Alarm { public string Title { get; set; } public string Description { get; set; } public DateTime AlarmTime { get; set; } public Alarm() { } public Alarm(string title, string description,DateTime alarmTime) { Title = title; Description = description; AlarmTime = alarmTime; } public override string ToString() { return "Title: " + Title +"\n"+ "Time: "+ AlarmTime.ToString("d") + "\n"+ "Description: " + Description; } } ~~~ 接下来再在XAML中添加TextBox控件如下,因为TextBox此时是用作显示而非输入,所以建议设置其的只读属性。数据绑定的核心就是Text属性中的那么一个Binding关键字。 ~~~ <TextBox x:Name="textBox1" FontSize="28" Height="150" Width="400" TextWrapping="Wrap" Text="{Binding}" IsReadOnly="True"/> ~~~ 但是光这样还不够,我们还需要在后台代码中将数据绑定到textBox1的DataContext(数据上下文)中。 ~~~ textBox1.DataContext = new Alarm( "First Alarm", "I need to study!", new DateTime(2015, 4, 11)); ~~~ 相信大家并不为觉得这个很难,相反我在学数据绑定的时候一上来就是一大堆理论,以至于我对数据一词有了阴影——所以我学数据结构非常痛苦。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fdc9b46.jpg "") 2.数据绑定到ComboBox 才保存一个闹钟没太大意思,我们多来几个。 ~~~ public ObservableCollection<Alarm> UsefulAlarm = new ObservableCollection<Alarm>(); public MainPage() { this.InitializeComponent(); UsefulAlarm.Add(new Alarm("First Alarm", "I need to study!", new DateTime(2015, 4, 11))); UsefulAlarm.Add(new Alarm("First Alarm", "Read a magzine!", new DateTime(2015, 4, 12))); UsefulAlarm.Add(new Alarm("First Alarm", "Write a blog!", new DateTime(2015, 4, 15))); UsefulAlarm.Add(new Alarm("First Alarm", "Travel", new DateTime(2015, 5, 15))); textBox1.DataContext = UsefulAlarm; } ~~~ 但是…… ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fdde03c.jpg "") 很显然我们用了ObservableCollection< T >类,它为数据绑定提供了一个集合,这是因为它实现了INotifyPropertyChanged和INotifyCollectionChanged接口。顾名思义,当属性改变时,它可以通知它所绑定的控件,并且如果你希望该空间能够同步更新,则将用于绑定的对象也实现INotifyPropertyChanged接口。这个类好归好,但相对于TextBox而言算有些高端了,以至于它无法显示出来。但是我们可以用ComboBox来代替它,我们的类并不需要修改,前面的UsefulAlarm实例化也都不用改,只需要将textBox1改成comboBox1即可。以下是新的ComboBox代码。 ~~~ <ComboBox Name="comboBox1" ItemsSource="{Binding}" FontSize="28" Height="150" Width="400"> <ComboBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Vertical" Margin="8"> <TextBox Width="350" TextWrapping="Wrap" Text="{Binding Title}" IsReadOnly="True"/> <TextBox Width="350" TextWrapping="Wrap" Text="{Binding Description}" IsReadOnly="True"/> <TextBox Width="350" TextWrapping="Wrap" Text="{Binding AlarmTime}" IsReadOnly="True"/> </StackPanel> </DataTemplate> </ComboBox.ItemTemplate> </ComboBox> ~~~ 在图示中我们也容易发现TextBox和ComboBox两个控件的Width属性的应用区别。在TextBox中,我们将数据绑定到Text中;而在ComboBox中,我们则是将数据绑定到ItemsSource中,简单的说就是ComboBox拿来所有的数据,再将它们分成小的细节发给它的子对象,这些子对象都在ComboBox的DataTemplate(数据容器)中。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fdefd54.jpg "") 在这里我们并没有用到前面所重载的ToString()函数,因为我们已经分别将Title、Description、AlarmTime绑定到相应的TextBox控件了。那图示中又为什么这些数据都是一行一行的表示呢,这都是布局控件StackPanel的功劳,全靠它的Orientation属性。如果将这个属性设置成Horizontal呢,那标题、描述已经时间就是全排在一行了。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fe119f3.jpg "") 3.数据绑定到ListBox 听说ListBox和ComboBox很类似哦,它们都是Box……XBox呀。博主我有点懒,那可不可以直接将ComboBox的名字改成ListBox就直接运行呢,答案是可以哦!那么区别到底在哪里呢?看看这张图就知道啦。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fe24d92.jpg "") 咦?怎么只有一条闹钟了?别惊慌……拖动右边的滚动条就可以查看到全部的闹钟咯。我真的只把ComboBox改成ListBox还有相应的Name属性(包括后台代码中的名字哦),以下就是完整的代码啦,我会骗你? ~~~ <ListBox Name="listBox1" ItemsSource="{Binding}" FontSize="28" Height="150" Width="400"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Vertical" Margin="8"> <TextBox Width="350" TextWrapping="Wrap" Text="{Binding Title}" IsReadOnly="True"/> <TextBox Width="350" TextWrapping="Wrap" Text="{Binding Description}" IsReadOnly="True"/> <TextBox Width="350" TextWrapping="Wrap" Text="{Binding AlarmTime}" IsReadOnly="True"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> ~~~ 4.数据绑定到ListView 看了前面的代码相信我没有骗你吧,童鞋们看到ListBox有没有想到ListView呢?我要是想说还是和前面一样只用改名字等就可以用ListView,你还是不信么? ~~~ <ListView Name="listView1" ItemsSource="{Binding}" FontSize="28" Height="150" Width="400"> <ListView.ItemTemplate> <DataTemplate> <StackPanel Orientation="Vertical" Margin="8"> <TextBox Width="350" TextWrapping="Wrap" Text="{Binding Title}" IsReadOnly="True"/> <TextBox Width="350" TextWrapping="Wrap" Text="{Binding Description}" IsReadOnly="True"/> <TextBox Width="350" TextWrapping="Wrap" Text="{Binding AlarmTime}" IsReadOnly="True"/> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView> ~~~ ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fe379b2.jpg "") 当然了,还是用右边的滚动条来下拉以查看所有的数据。不过ListView君的最佳姿势不是这样哦,将Height改为600才是呢。看下图——这才是高大上的ListView君嘛! ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fe4f49c.jpg "") 好了不玩了,GridView也是可以这样弄得,不信你试试。 ### 再谈数据绑定 1.我们为什么要用数据绑定 很显然,我们不可能把所有的数据全部固定在特定的控件上。比如,游戏的积分、设定的闹钟、天气预报甚至的通讯类的消息,它们都并非是一成不变的。但是也并非所有的控件都需要绑定,比如你的App的名字、发送消息时所用的发送按钮上面的文本等。 2.那数据和UI之间又有哪些关系呢 首先我们得明确,数据的显示和其后台的管理是不一样的。数据与UI绑定之后,我们的数据就可以在这两者之间进行沟通,如果数据发生变化时,绑定到数据的UI则会自动将相应的属性进行调整,不仅仅是前面用到的Text属性,还有FontSize、Width、Foreground、Image属性都可以。 3.数据绑定到底是绑定什么 首先,我们得有绑定源,这些就是我们需要绑定的数据,没有数据,即使你绑定了,它也显示不出来。 其次,我们还需要绑定目标,也就是Framework类的DependencyProperty属性,说得白话文点就是将数据绑定到UI的相应属性上。 最后,我们还需要一个Binding对象,它就像是搬运工,没有它,数据也是无法动弹的。它能够帮助我们将数据从数据源移动到绑定目标,并且将绑定目标的相应消息通知给绑定源。它还有一些巧妙的工具,能够将绑定源的数据加工成特定的格式。 4.绑定源有哪些 所有的公共语言运行时对象,我们前面用的Alarm类就是这种对象,另外UI元素也是哦。 5.听说有的搬运工只能将数据源的数据一次性搬到绑定目标后就不再搬了,而有的搬运工则会在数据修改后再搬一次,甚至还有的能够在绑定目标更改后再将数据搬回到数据源 OneTime绑定:这个搬运工的工作就是第一种,它只负责在创建时将源数据更新到绑定目标。 OneWay绑定:这是系统默认的搬运工,它是第二种,负责在创建时以及源数据发生更改时更新绑定目标。 TwoWay绑定:这个搬运工则是第三种,它能够在绑定源和绑定目标的一边发生更改时同时更新绑定源和绑定目标。但它在一种时候却会偷懒,那就是对于TextBox.Text每次点击之后,它就不会将这个Text属性的更改更新到绑定源。不过如果碰到Boss,它也只能继续搬了。那就是将Binding.UpdateSourceTrigger设置成PropertyChanged。而默认情况下,只有TextBox失去焦点时才会去更新。 以下分别是OneWay和TwoWay的例子: ~~~ <StackPanel Width="240" Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center"> <Slider Name="slider1" Minimum="0" Maximum="100"/> <TextBox FontSize="30" Text="{Binding ElementName=slider1,Path=Value,Mode=OneWay}" /> </StackPanel> ~~~ 拖动滑动条,就可以看到在TextBox中显示它的值的变化了。如果希望它只变化一次,那就将代码中的OneWay改成OneTime即可。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fe64641.jpg "") ~~~ <StackPanel Width="240" Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center"> <TextBox FontSize="30" Name="textBox" Height="60" Text ="{Binding ElementName=listBox1, Path=SelectedItem.Content, Mode=TwoWay}"> </TextBox> <ListBox FontSize="30" Name="listBox1"> <ListBoxItem Content="Item 1"/> <ListBoxItem Content="Item 2"/> <ListBoxItem Content="Item 3"/> <ListBoxItem Content="Item 4"/> </ListBox> </StackPanel> ~~~ 如下图所示,点击Item 1后TextBox则会显示相应的Item 1,将TextBox中的Item 1修改为Item 5后再ListBox中也自动修改成了Item5。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fe76859.jpg "") ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fe8a5fd.jpg "") 简单示例:Foreground的数据绑定 前面已经说到了Foreground也可以绑定,想不想试试呢。我们现在TextBox中写一个TextBox,然后在后台代码中添加一个绑定就可以了。这个和前面的比较简单,这里只是用来引出后面的东东哦 ~~~ <TextBox Name="textBox" Width="200" Height="100" IsReadOnly="True" FontSize="32" Text="Text" Foreground="{Binding ForeBrush}"/> ~~~ ~~~ textBox.Foreground = new SolidColorBrush(Colors.BlueViolet); ~~~ # 更改通知 1.Silder绑定到TextBlock,不使用更改通知 首先定义一个简单的类BindingSlider,同时在XAML中作如下定义。 ~~~ public class BindingSlider { private int sliderValue; public int SliderValue { get { return sliderValue; } set { sliderValue = value; } } } ~~~ ~~~ <StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center"> <Slider Name="slider1" Minimum="0" Maximum="100" Width="200" Value="{Binding SliderValue,Mode=TwoWay}"/> <Button x:Name="button" Content="Button" Width="200" Click="button_Click"/> <TextBlock Name="textBlock" FontSize="30"/> </StackPanel> ~~~ 虽然这里只是用到了OneWay传递,但还是需要使用TwoWay。因为在这里OneWay是指从BindingSlider类的SliderValue属性单向传递到Slider控件的Value属性。但我们需要的则是Slider控件的Value属性单向传递到BindingSlider类的SliderValue属性,所以才得使用TwoWay方式。 ~~~ BindingSlider bindingSlider = new BindingSlider(); public MainPage() { this.InitializeComponent(); slider1.DataContext = bindingSlider; } private void button_Click(object sender, RoutedEventArgs e) { textBlock.Text = bindingSlider.SliderValue.ToString(); } ~~~ 首先实例化BindingSlider类,再在后台代码中奖bindingSlider对象绑定到slider1的数据上下文。最后通过Click事件来将bindingSlider对象的SliderValue属性传递给textBlock控件的Text属性。 这里的效果就是,拖动Slider但是TextBlock不会有变化,而需要Button来不断的更改TextBlock的Text。如果想要TextBlock的Text能够根据Slider实时的更改,这就需要”更改通知“了。 2.Silder绑定到TextBlock,使用更改通知 既然要使用通知更改的技术,那就可以在XAML代码中将Button控件删除掉了,包括后台代码中的Click事件。 紧接着来修改BindingSlider类,首先得使用INotifyPropertyChanged接口。这个接口有PropertyChanged事件,而这个事件则会告知绑定目标绑定源已经发生修改,这样绑定目标也会实时的进行更改。在新的set中,我们将SliderValue值传递到NotifyPropertyChanged中。 ~~~ public class BindingSlider :INotifyPropertyChanged { private int sliderValue; public int SliderValue { get { return sliderValue; } set { sliderValue = value; NotifyPropertyChanged("SliderValue"); } } public event PropertyChangedEventHandler PropertyChanged; public void NotifyPropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } } ~~~ 最后我们还需要将bindingSlider对象绑定到textBlock的数据上下文。 ~~~ BindingSlider bindingSlider = new BindingSlider(); public MainPage() { this.InitializeComponent(); slider1.DataContext = bindingSlider; textBlock.DataContext = bindingSlider; } ~~~ 这样一来就全部更改完成了,试试就会发现TextBlock的Text会根据Slider的拖动而实时修改了。 # 值转换器 有时候默认的输出方式不能满足我们的需要,比如前面的OneWay示例,可能我们需要的是在TextBox中显示“开始加载“、”加载一半了“、”很快就加载完了“以及”已经加载好“等,甚至还可以让其能够转换成英文哦。 那么首先新建一个类SliderValueConverter.cs,然后实现IValueConverter接口。然后按自己的需要写它的Converter方法即可。 ~~~ public class SliderNotifyAndConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, string language) { string valueTextBlock; string parameterValue = parameter.ToString(); double valueSlider = (double)value; if (valueSlider > 0&&valueSlider<=5) { if (parameterValue == "zh-cn") valueTextBlock = "开始加载"; else valueTextBlock = "Starts to load"; } else if (valueSlider >= 45 && valueSlider <= 55) { if (parameterValue == "zh-cn") valueTextBlock = "加载一半了"; else valueTextBlock = "loaded half"; } else if (valueSlider >= 90&&valueSlider<100) { if (parameterValue == "zh-cn") valueTextBlock = " 很快就加载完了"; else valueTextBlock = "finished loading very quickly"; } else if (valueSlider == 100) { if (parameterValue == "zh-cn") valueTextBlock = " 已经加载好"; else valueTextBlock = "loaded"; } else { if (parameterValue == "zh-cn") valueTextBlock = "加载中"; else valueTextBlock = "Loading"; } return valueTextBlock; } public object ConvertBack(object value, Type targetType, object parameter, string language) { throw new NotImplementedException(); } } ~~~ 最后还需要在XAML中添加如下代码哦,值转换器Converter所使用的静态资源已经在 ~~~ <Page.Resources> <local:SliderNotifyAndConverter x:Key="SliderNotifyAndConverterResources"/> </Page.Resources> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <StackPanel Name="stackPanel" Width="450" Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center"> <Slider Name="slider1" Minimum="0" Maximum="100" Value="95"/> <TextBlock FontSize="30" Text="{Binding ElementName=slider1, Path=Value, Converter={StaticResource SliderNotifyAndConverterResources}, ConverterParameter='zh-cn'}"/> <TextBlock FontSize="30" Text="{Binding ElementName=slider1, Path=Value, Converter={StaticResource SliderNotifyAndConverterResources}, ConverterParameter='en-us'}"/> </StackPanel> </Grid> ~~~ 以下是Slider的Value取不同值时TextBlock的不同显示。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fea0dc4.jpg "") ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033feb2d7e.jpg "") 这里仅仅是一个比较简单的示例,在项目产品中往往有数据绑定的地方都会有值转换器的,就像C++中经常重载ostream一样。
';

Windows App开发之编辑文本与绘制图形

最后更新于:2022-04-01 16:23:37

# 编辑文本及键盘输入 相信大家都会使用TextBox,但如果要让文本在TextBox中换行该怎么做呢?将TextWrapping属性设置为Wrap,将AcceptsReturn属性设置为True就好咯。 PasswordBox很明显就是一个密码框了,和其他的控件相比其有2个特殊之处,一个是其可以用MaxLength来控制最大的长度,一个是用PasswordChanged来捕捉密码的改名。显然比如QQ密码的MaxLength就是16位了,而PasswordChanged可以用来监测比如用户设置的密码和用户名是否相同。 大家在用电脑或者手机输入时偶尔键盘是出来的26字母拼音或是26字母英文亦或是10个数字对吧,那这个是怎么实现的呢?同样也是很简单的噢!直接在TextBox上用InputScope属性就好啦,比如有Default、TelephoneNumber、EmailSmtpAddress、Url、Search、Chat等可以设置。 除了在XAML中设置InputScope属性外,也可以在后台C#文件中设置。 ~~~ InputScope inputScope = new InputScope(); InputScopeName inputScopeName= new InputScopeName(); inputScopeName.NameValue = InputScopeNameValue.TelephoneNumber; inputScope.Names.Add(scopeName); phoneNumberTtBox.InputScope = scope; ~~~ 在这段代码中,phoneNumberTtBox是TextBox的名字哟,或者也可以简写这段代码的: ~~~ phoneNumberTtBox.InputScope = new InputScope() { Names = {new InputScopeName(InputScopeNameValue.TelephoneNumber)} }; ~~~ 除此之外,我们还可以给RichEditBox控件设置IsSpellCheckEnabled属性让这个文本控件启用拼写检查。另外值得注意的是TextBox控件的拼写检查只在Windows Phone上启用,在Windows上市禁用的。而文本预测属性在TextBox和RichEditBox以及在Windows和Windows Phone上都是可用的哦,也就是IsTextPredictionEnabled。 # 情节提要动画与关键帧动画 ### 简单动画示例 因为下面这些Rectangle都是在ItemsControl中的,因为在容器控件中应用主题样式时,其所有的子对象也都会继承下来。 ~~~ <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <ItemsControl Grid.Row="1" x:Name="itemsControlRectangle"> <ItemsControl.ItemContainerTransitions> <TransitionCollection> <EntranceThemeTransition/> </TransitionCollection> </ItemsControl.ItemContainerTransitions> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <WrapGrid Height="400"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.Items> <Rectangle Fill="Red" Width="100" Height="100" Margin="12"/> <Rectangle Fill="Wheat" Width="100" Height="100" Margin="12"/> <Rectangle Fill="Yellow" Width="100" Height="100" Margin="12"/> <Rectangle Fill="Blue" Width="100" Height="100" Margin="12"/> <Rectangle Fill="Green" Width="100" Height="100" Margin="12"/> <Rectangle Fill="Gray" Width="100" Height="100" Margin="12"/> <Rectangle Fill="White" Width="100" Height="100" Margin="12"/> <Rectangle Fill="Gainsboro" Width="100" Height="100" Margin="12"/> <Rectangle Fill="Magenta" Width="100" Height="100" Margin="12"/> <Rectangle Fill="CadetBlue" Width="100" Height="100" Margin="12"/> <Rectangle Fill="NavajoWhite" Width="100" Height="100" Margin="12"/> <Rectangle Fill="Khaki" Width="100" Height="100" Margin="12"/> </ItemsControl.Items> </ItemsControl> </Grid> ~~~ ### 情节提要动画 就像我们前面介绍的定义样式资源一样,我们也可以将动画设为资源。 ~~~ <Page.Resources> <Storyboard x:Name="storyboardRectangle" > <DoubleAnimation Storyboard.TargetName="rectangle" Storyboard.TargetProperty="Opacity" From="1.0" To="0" Duration="0:0:1" AutoReverse="True" RepeatBehavior="Forever"/> </Storyboard> </Page.Resources> <Grid> <Rectangle x:Name="rectangle" Width="200" Height="130" Fill="Blue"/> </Grid> ~~~ 在理解这些代码意思之前,还是先让动画跑起来,你可以加上一个Button并设置其Click事件,也可以在MainPage方法下直接写如下代码: ~~~ storyboardRectangle.Begin(); ~~~ 运行应用后,Rectangle的透明度就会渐渐的消失而后出现。 在上面这个示例中,我们为Rectangle的Opacity(透明度)属性设置了动画,Storyboard通常存放在 ~~~ Storyboard.TargetProperty="(rectangle.Fill).(SolidColorBrush.Color)" ~~~ 如果你已经定义了TargetName属性为rectangle,那么Fill前的rectangle和点都可以去掉。 左右两个括号都是必要的,它表示一个属性的名称。中间的点意味着要先获取第一个括号的属性,也就是设置动画的对应对象,然后进入到其对象模型中,此处是Color。官网上还给出了其它示例: (UIElement.RenderTransform).(TranslateTransform.X) 应用到RenderTransform上,并创建TranslateTransform的X值的动画 (Shape.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Color) 应用到Fill上,并在LinearGradientBrush的GradientStop内创建Color的动画,这里方括号内的数字表示索引,表示集合中的一项,索引从0开始 (UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X) 应用到RenderTransform上,并创建TranslateTransform 我们还注意到,动画中还有From和To属性,顾名思义,From表示动画的开始值,To表示结束值。 如果没有定义From值,那么动画起始值为该对象属性的当前值。 如果想要设置一个和起始值相对的结束值,建议使用By属性。 动画在这3个属性中至少应该设置一个,否则动画便不会更改值,且这3个属性也无法同时存在。 我们还可以用设置AutoReverse属性为真以使动画才结束后自动进行反向播放,但反向播放完后不会再继续播放。 设置RepeatBehavior属性为“1x”表示动画的播放次数,或者也可以直接设为“Forever”,让其永远播放。 如果动画较多的情况下,我们哈可以设置BeginTime来使不同的动画错开播放。 ### 关键帧动画 什么是关键帧动画? 关键帧动画建立在上文的情节提要动画概念智商,它令动画沿着一条时间线来逐步达到多个目标值,也就是说如果要让上文的Fill属性从Blue变化到Lime之间还可以令其先变化到Red或Orange等。 更为巧妙的是,你可以同时指定不同的属性来制作复杂的动画。 如果稍微会一点Flash,对于关键帧的概念肯定没有问题。 1.线性关键帧 我们为动画设置一个KeyTime来表示间隔的时间戳,例如我们可以设置4个时间戳为:KeyTime=”0:0:0”、”0:0:2”、”0:0:8”、”0:0:9”,可以看到动画在中间部分时跳跃性非常之大。但其动画都是缓慢变化的,因为这是线性的,还有一种另外一种关键帧它会让动画在时间戳上产生突变而不是渐变,这就是离散式关键帧(就像概率论中的离散型和连续型一样)。 2.样条关键帧 其主要通过KeySpline属性来建立过渡,例如KeySpline=”0.1,0.1 0.7.0.8”,这里有两个点,分别对应贝塞尔曲线的第一个控制点和第二个控制点,描述了动画的加速情况。关于贝塞尔曲线,建议大家看看维基百科,在图形化编程中非常常用。 3.缓动关键帧 这种模式就更加高级了,它由多个预定义好的数学公式来控制。以下是的缓动函数列表来源于网络: BackEase:动画开始在指定路径上运动前稍微收缩动画的运行。 BounceEase:创建回弹效果。 CircleEase:使用圆函数创建加速或减速的动画。 CubicEase:使用函数 f(t) = t3 创建加速或减速的动画。 ElasticEase:创建一个动画,模拟弹簧的来回振荡运动,直到它达到停止状态。 ExponentialEase:使用指数公式创建加速或减速的动画。 PowerEase:使用公式 f(t) = tp 创建加速或减速的动画,其中 p 等于 Power 属性。 QuadraticEase:使用函数 f(t) = t2 创建加速或减速的动画。 QuarticEase:使用函数 f(t) = t4 创建加速或减速的动画。 QuinticEase:使用函数 f(t) = t5 创建加速或减速的动画。 SineEase:使用正弦公式创建加速或减速的动画。 # 绘制图形 ### Rectangle 我们开篇先介绍一个之前用过,也是比较简单的Rectangle。简单的矩形就只用定义长和宽了,但如果要有圆角的话呢,用RadiusX和RadiusY就好。那么RadiusX和RadiusY到底是什么呢?看看下图就知道了。 ~~~ <Rectangle Fill="Yellow" Width="300" Height="200" Stroke="Blue" StrokeThickness="10" RadiusX="80" RadiusY="40"/> ~~~ ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fc4a608.jpg "") 和Rectangle类似,Border也可以创建矩形,而且后者还可以有自对象以及会自动调整大小,前者只能有固定的大小哦。 ### Ellipse 看到这个名字大家应该都知道是什么意思吧,如果要定义成圆的话让Height和Width属性相等即可。 那童鞋们都知道ProgressRing是由6个Ellipse组成的吗,RadioButton也是由2个同心的Ellipse组成的哦。 ~~~ <Ellipse Fill="Blue" Height="200" Width="350"/> ~~~ ### Polygon Polygon则显得比较自由,只需要定义出各个顶点,它就会将这些点连接起来。那么我们可能会有疑问,需不需要确定图形的起始点和终点呢?答案是不用的,因为Polygon会自动将终点和起始点连接起来(它会假设图形是闭合的)。 ~~~ <Polygon Fill="Green" Points="0,0,100,0,100,100,0,100 "/> ~~~ ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fc61532.jpg "") 如果要在后台C#文件中来写的话呢,原本的Point则由PointCollection来定义所有点后添加到一起。 ### Line Line的使用也比较简单,但有一点要注意,必须设置好Stroke和StrokeThickness的属性值,否则Line就不会显示出来。原因很简单,因为它是直线。 ~~~ <Line Stroke="Red" StrokeThickness="10" X1="100" Y1="0" Y2="400" X2="400"/> ~~~ ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fc7446d.jpg "") ### Path 最后上台的自然是最厉害的啦,先上图。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fc86e29.jpg "") ~~~ <Path Stroke="Gold" StrokeThickness="7" Data="M 0,0 C 100,200 50,200 40,150 H 200 V 100 "/> ~~~ 前两个属性用过多次了,Data却还挺复杂的。这里有3个命令,M、C、H和V。如果按英文来记可能会容易些吧,分别是:Move、Control、Horizontal和Vertical。 那么,重头戏来了,先看图^_^ ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fc9eac9.jpg "") 接着上代码。 ~~~ <Path Stroke="Black" StrokeThickness="1" Fill="red"> <Path.Data> <GeometryGroup> <RectangleGeometry Rect="5,5 180,10" /> <RectangleGeometry Rect="5,5 95,180" /> <RectangleGeometry Rect="90,175 95,180"/> <RectangleGeometry Rect="5,345 180,10" /> <EllipseGeometry Center="95, 180" RadiusX="20" RadiusY="30"/> <PathGeometry> <PathGeometry.Figures> <PathFigureCollection> <PathFigure IsClosed="true" StartPoint="50,50"> <PathFigure.Segments> <PathSegmentCollection> <BezierSegment Point1="100,180" Point2="125,100" Point3="150,50"/> </PathSegmentCollection> </PathFigure.Segments> </PathFigure> <PathFigure IsClosed="true" StartPoint="40,310"> <PathFigure.Segments> <PathSegmentCollection> <BezierSegment Point1="90,180" Point2="115,250" Point3="140,310"/> </PathSegmentCollection> </PathFigure.Segments> </PathFigure> </PathFigureCollection> </PathGeometry.Figures> </PathGeometry> </GeometryGroup> </Path.Data> </Path> ~~~ 这张图花了我好久时间呢,希望大家也都会画,虽然作用不大,不过花着玩玩也不错。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fcb2530.jpg "") 我在图上大概加了一些标注啦,另外RectangleGeometry的Rect属性有2个值,后者是相对于前者增加的长度哦。 最难的部分是BezierSegment,也就是贝赛斯曲线,其中StartPoint和Point3分别为起点和终点,而Point1和Point2不是路径哟,只是给曲线的一个参考偏移方向。具体大家可以上维基百科看看。 # 画笔与图像 画笔想必大家都不陌生,这里系统的介绍一下好了。先来介绍纯色画笔。 ### 纯色画笔 最简单的纯色画笔就是已经定义好名字的啦,比如Red和Green这种,据说一共有256种已命名的,所以基本已经够用啦。XAML解析器会自动将这些颜色名称链接到Color结构。 还有就是传说中的十六进制颜色值,它可以定义精确的24位颜色值,其中有8位用于SolidColorBrush。如下代码所示的,alpha=”FF”,红色=”55”,绿色=”00”,蓝色=”88”。 ~~~ <Rectangle Width="200" Height="100" Fill="#FF550088" /> ~~~ 还有一种称为属性元素语法。具体用法如下,其中Opacity就是透明度咯。 ~~~ <Rectangle Width="200" Height="100"> <Rectangle.Fill> <SolidColorBrush Color="Yellow" Opacity="0.3" /> </Rectangle.Fill> </Rectangle> ~~~ ### 渐变画笔 除了纯色画笔外,还有渐变画笔。小时候学PhotoShop的时候最喜欢渐变画笔了。 LinearGradientBrush会沿着一条称为渐变轴直线来进行渐变以绘制一个区域。我们还是拿Rectangle来做示例。 ~~~ <Rectangle Width="200" Height="100"> <Rectangle.Fill> <LinearGradientBrush StartPoint="0,0" EndPoint="1,1"> <GradientStop Color="Green" Offset="0.0" x:Name="GradientStop1"/> <GradientStop Color="Blue" Offset="0.25" x:Name="GradientStop2"/> <GradientStop Color="Wheat" Offset="0.7" x:Name="GradientStop3"/> <GradientStop Color="Yellow" Offset="0.75" x:Name="GradientStop4"/> <GradientStop Color="Gold" Offset="1.0" x:Name="GradientStop5"/> </LinearGradientBrush> </Rectangle.Fill> </Rectangle> ~~~ ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fccbf0c.jpg "") 通过改变StartPoint和EndPoint的属性值可以创建各种渐变哦,比如垂直和水平方向的渐变,还可以颠倒渐变方向,甚至还可以加快渐变速度呢。 ### 直接添加图片 除了用着两种画笔外,还可以直接将图片添加进来呢。 ~~~ <Ellipse Height="100" Width="200"> <Ellipse.Fill> <ImageBrush ImageSource="9327.jpg"/> </Ellipse.Fill> </Ellipse> ~~~ 效果如下咯,主要是有一张合适的图片啦。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fce5bc1.jpg "") 既然用到了ImageBrush,那就来看看Image和ImageBrush的区别好了。前者主要用来呈现图像,后者则为其他对象绘制为一个图像。 ### Stretch属性 对于Image,我们可以来拉伸图像,也就是Stretch属性: None:图像不经过拉伸。如果源图像比所留给Image的区域大,那么就会被剪切。 Uniform:按照纵横比来缩放图像。 UniformToFill:按照纵横比来填满所有区域,这意味着可能会有一部分不可见。 Fill。因为不保留纵横比而填满屏幕,所以图像部分全部可见,但会产生画面变形(失真)。 具体效果见下图(来源于网络)。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fd1dab2.jpg "") ### Clip属性 用Clip属性可以对图像进行剪裁,Rect属性在上一篇博客中用过许多次,前2个值为起始点的X轴和Y轴坐标,后2个值为终点的X轴和Y轴坐标。 ~~~ <Image Source="9327.jpg"> <Image.Clip> <RectangleGeometry Rect="10,10,100,100"/> </Image.Clip> </Image> ~~~ Image和ImageBrush能处理的图像格式有如下几种: JPEG XR 图标(ICO) 位图(BMP) 图像交换格式(GIF) 联合图像专家组(JPEG) 可移植网络图像(PNG) 标记图像文件格式(TIEF)
';

前言

最后更新于:2022-04-01 16:23:35

> 原文出处:[UWP通用应用开发](http://blog.csdn.net/column/details/nomasp-uwp.html) 作者:[柯于旺](http://blog.csdn.net/nomasp) **本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!** # UWP通用应用开发 > Windows10 引入了通用 Windows 平台 (UWP),面向 UWP 的应用不仅可以调用对所有设备均通用的 WinRT API,还可以调用特定于要运行应用的设备系列的 API(包括 Win32 和 .NET API)。
';