本文主要是介绍UWP--简单日程制作,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
工具:Visual Studio 2017、Blend
语言:XAML,C#
日程应用的要求如下:实现自适应UI,即应用样式跟随屏幕宽度改变而改变;实现日程创建,删除与查看功能。
说明:在这个日程的制作中,具体的xaml和c#语法不再赘述,不了解的地方可以自行百度。如图,先来看看最终样式:(说明:左侧页面标题为MyLists,右侧页面为Details;日程名称为ListItem)
1. 完整视图:(width≥800px)
2. Detail消失: (600px≤width<800px)
3. Image消失:(width<600px) 4. Detail界面:(width<800px, 点击左侧日程)
下面进行具体实现:
- 总体实现规划:
主要分为三个部分:视图View(.xaml)、逻辑Logic(.xaml.cs)和数据绑定;
1. 视图部分:(XAML分为两个页面,MainPage和NewPage)
MainPage主要写左侧日程列表的布局,并在页尾加上一个可以导向到NewPage的Frame框架;布局采用Grid+ListView来写,Grid分为两行,第一行写标题,第二行分为两列,一边容纳ListView列表(即主页面的主要部分),另一边容纳右边详情(Detail);关于页面自适应,使用VisualStateGroup来实现,在Blend中设计三个不同的VisualState状态,来控制页面宽度不同时Detail界面和Image元素是否显示,其中比较关键的是Grid.ColumnSpan的应用,此属性表示所选Frame所占用的列数;关于Image的自适应实现,VisualStateGroup必须写在DateTemplate里面,并且用UserControl标签包含;最后在MainPage页面末尾加上AppBarbutton即可。
Newpage的实现比较简单,采用Grid和ScrollViewer、StackPanel标签来实现,Grid也分为两行,第一行可以写标题(Detail),第二行用来容纳ScrollViewer标签,ScrollViewer实现滚动条,里面写上用Stackpanel包含的各个空间元素即可以实现;最后页面末尾加上AppBarButton即可;
页面布局大致就是如此,最后可以加上背景图,利用Page.Background标签。
2. 逻辑部分:(逻辑部分主要也分为MainPage和NewPage)
MainPage下主要实现CheckBoxClick、ListView_ItemClick、AddBarButtonClick、MenuFlyEdit_Click、MenuFlyDelete_Click五个Click事件的函数及相应控件的数据绑定。
NewPage 下主要实现Create_Update、Cancel、GetPicture、AddBarbuttonClick、DeleteBarButtonClick五个绑定函数的功能。
3. 数据绑定: 数据绑定是一种把数据绑定到用户界面元素(控件)的通用机制。可以使用x:Bind即Binding来实现,推荐使用x:Bind(x:Bind可以解决大部分绑定问题)
- 代码文件结构:
第一次做UWP开发,并且也是第一次使用MVVM框架,所以有些使用不当的地方,还望谅解。文件具体目录结构如下:
Models文件夹下是ListItem.cs文件,书写日程的类模板;ViewModels文件夹下是ListItemViewModels.cs文件,书写ListItem的处理方法。由于之后的视图、逻辑还有数据绑定实现过程需要ListItem类及其处理方法,所以先分别实现这两个模块:
ListItem.cs:
namespace List.Models
{class ListItem : INotifyPropertyChanged{public event PropertyChangedEventHandler PropertyChanged;//public string Id { get; set; }public ImageSource Img { get; set; }public double Size { get; set; }public string Title { get; set; }public string Detail { get; set; }public DateTimeOffset Date { get; set; }public bool? Finish { get; set; }public ListItem(ImageSource img, double size, string title,string detail, DateTimeOffset date){//this.Id = Guid.NewGuid().ToString();this.Img = (img == null ? new BitmapImage(new Uri("Assets/pic3.ico")) : img);this.Size = size;this.Date = date;this.Title = title;this.Detail = detail;this.Finish = false;}private void NotityPropertyChanged(string propertyname){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));}}
}
ListItemViewModels.cs:
namespace List.ViewModels
{class ListItemViewModel{private ObservableCollection<Models.ListItem> allItems = new ObservableCollection<Models.ListItem>();public ObservableCollection<Models.ListItem> AllItems { get { return this.allItems; } }private Models.ListItem selectedItem;public Models.ListItem SelectedItem{get { return selectedItem; }set { this.selectedItem = value; }}public void AddListItem(ImageSource img, double size, string title, string detail, DateTimeOffset date){this.allItems.Add(new Models.ListItem(img, size, title, detail, date));}public void RemoveListItem(Models.ListItem SelectedItem1){this.allItems.Remove(SelectedItem1);this.selectedItem = null;}public void UpdateListItem(ImageSource img, double size, string title,string detail, DateTimeOffset date){this.selectedItem.Img = img;this.selectedItem.Size = size;this.selectedItem.Title = title;this.selectedItem.Detail = detail;this.selectedItem.Date = date;this.selectedItem = null;}}
}
- 详细实现流程:
- 实现简单逻辑,完成控件的实现,分为MainPage和NewPage两个界面:
XMAL代码如下:(此代码中实现了上述图片中的所有控件,注意数据绑定x:Bind的使用)
<CheckBox x:Name="checkBox" Grid.Column="0" IsChecked="{x:Bind Finish, Mode=TwoWay}" Checked="Checked" Unchecked="Unchecked" Margin="7 5 3 5" Height="32" Width="32"/>
<Image x:Name="image" Grid.Column="1" Source="{x:Bind Img, Mode=TwoWay}" Height="80" Width="80" />
<Line x:Name="line" Grid.Column="2" Stretch="Fill" Stroke="Black" X1="1" StrokeThickness="3" VerticalAlignment="Center" Opacity="0"/>
<TextBlock Grid.Column="2" Text="{x:Bind Title, Mode=TwoWay}" VerticalAlignment="Center" FontSize="20"/>
<AppBarButton Grid.Column="3" Icon="Setting" VerticalAlignment="Center" Margin="0 23 0 10" HorizontalAlignment="Left" IsCompact="True"><AppBarButton.Flyout><MenuFlyout><MenuFlyoutItem Text="Edit" Click="MenuFlyoutEdit_Click" /><MenuFlyoutItem Text="Delete" Click="MenuFlyoutDelete_Click"/></MenuFlyout></AppBarButton.Flyout>
</AppBarButton>
c#代码如下:(下面的代码可以用一个函数实现,即CheckBox的Click函数)
private void Checked(object sender, RoutedEventArgs e)
{var parent = VisualTreeHelper.GetParent(sender as DependencyObject);Line line = VisualTreeHelper.GetChild(parent, 2) as Line;line.Opacity = 1;
}private void Unchecked(object sender, RoutedEventArgs e)
{var parent = VisualTreeHelper.GetParent(sender as DependencyObject);Line line = VisualTreeHelper.GetChild(parent, 2) as Line;line.Opacity = 0;
}
b)实现NewPage中Details列表的样式布局,并进行逻辑判断实现Create和Cancel功能(利用MessageDialog进行失败/成功创建的提示);
xaml代码如下:(采用StackPanel和Grid两个布局容器来实现,在此我将自定义选择图片和放大缩小功能实现了,其中有部分数据绑定(需要使用Binding, x:Bind在此实现不了)的实现,Image的ScaleX,ScaleY与slider的Value绑定)
<StackPanel Height="Auto" HorizontalAlignment="Center"><Image x:Name="pic" Width="300" Source="Assets/pic3.ico" Stretch="Fill" Height="180" RenderTransformOrigin="0.5,0.5"><Image.RenderTransform><CompositeTransform ScaleX="{Binding Value, ElementName=slider}" ScaleY="{Binding Value, ElementName=slider}"/></Image.RenderTransform></Image><Slider Width="300" Minimum="0.4" Maximum="1.0" StepFrequency="0.001" x:Name="slider"/><RelativePanel Grid.Row="2" Width="300" HorizontalAlignment="Center"><AppBarButton Icon="Pictures" Label="select" RelativePanel.AlignRightWithPanel="True" Click="GetPicture" /></RelativePanel><TextBlock HorizontalAlignment="Left" Text="Title" TextWrapping="Wrap" VerticalAlignment="Bottom" Margin="1"/><TextBox x:Name="title" HorizontalAlignment="Left" Text="" VerticalAlignment="Bottom" Width="300" Margin="5"/><TextBlock HorizontalAlignment="Left" Text="Detail" TextWrapping="Wrap" VerticalAlignment="Bottom" Margin="1"/><TextBox x:Name="detail" HorizontalAlignment="Left" Text="" VerticalAlignment="Bottom" Width="300" Height="72" Margin="5"/><TextBlock HorizontalAlignment="Left" Text="Due Date" TextWrapping="Wrap" VerticalAlignment="Bottom" Margin="1"/><DatePicker x:Name="datePicker" HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="300" Margin="5"/>
</StackPanel><Grid Width="300"><Button x:Name="create_update" Click="Create_Update" Content="Create" HorizontalAlignment="Left" /><Button x:Name="cancel" Click="Cancel" Content="Cancel" HorizontalAlignment="Right" />
</Grid>
c#代码如下:
private async void Create_Update(object sender, RoutedEventArgs e)
{string message = "";if (title.Text == "")message += "Title";if (detail.Text == ""){if (message != "") message += ",Detail";else message += "Detail";}if (message != "") message += "不得为空\n";if (datePicker.Date < DateTimeOffset.Now.LocalDateTime.AddDays(-1))message += "时间不得小于当前日期";if (message != "")await new MessageDialog(message).ShowAsync();else if (create_update.Content.ToString() == "Create"){Frame rootFrame = Window.Current.Content as Frame;MainPage.ViewModel1.AddListItem(pic.Source, slider.Value, title.Text, detail.Text, datePicker.Date);rootFrame.Navigate(typeof(MainPage));await new MessageDialog("Create successfully!").ShowAsync();}else{Frame rootFrame = Window.Current.Content as Frame;MainPage.ViewModel1.UpdateListItem(pic.Source, slider.Value, title.Text, detail.Text, datePicker.Date);rootFrame.Navigate(typeof(MainPage));await new MessageDialog("Update successfully!").ShowAsync();}
}private void Cancel(object sender, RoutedEventArgs e)
{pic.Source = Item.Img; //Item是中间变量,存储的是Cancel前的日程信息title.Text = Item.Title;slider.Value = Item.Size;detail.Text = Item.Detail;datePicker.Date = Item.Date;
}private void GetPicture(object sender, RoutedEventArgs e)
{var getSelectPicture = new GetSelectPicture();getSelectPicture.selectPic(pic);
}
上面的GetPicture函数中的GeiSelectPicture需要自己实现一个内部类:
internal class GetSelectPicture
{public async void selectPic(Image pic){var fop = new FileOpenPicker();fop.ViewMode = PickerViewMode.Thumbnail;fop.SuggestedStartLocation = PickerLocationId.PicturesLibrary;fop.FileTypeFilter.Add(".jpg");fop.FileTypeFilter.Add(".jpeg");fop.FileTypeFilter.Add(".png");fop.FileTypeFilter.Add(".gif");Windows.Storage.StorageFile file = await fop.PickSingleFileAsync();try{using (IRandomAccessStream fileStream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read)){BitmapImage bitmapImage = new BitmapImage();await bitmapImage.SetSourceAsync(fileStream);pic.Source = bitmapImage;}}catch (Exception){return;}}
}
2. 实现+(右下角新建按钮),<-(左上角back按钮)页面跳转功能;
a) +(右下角新建按钮)功能实现:
XMAL代码:
<Page.BottomAppBar><CommandBar><AppBarButton x:Name="AddAppBarButton" Icon="Add" Label="Add" Click="AddBarButtonClick"/></CommandBar></Page.BottomAppBar>
c#代码如下:
private void AddBarButtonClick(object sender, RoutedEventArgs e)
{if (right.Visibility.ToString() == "Collapsed"){Frame rootFrame = Window.Current.Content as Frame;rootFrame.Navigate(typeof(NewPage));}elseright.Navigate(typeof(NewPage));
}
b) <-(左上角back按钮)页面跳转功能实现:(写到App.xaml.cs里面,一下代码即使事件委托的使用,需要好好理解事件委托)
protected override void OnLaunched(LaunchActivatedEventArgs e)
{Frame rootFrame = Window.Current.Content as Frame;if (rootFrame == null){rootFrame = new Frame();rootFrame.Navigated += OnNavigated;SystemNavigationManager.GetForCurrentView().BackRequested += OnBackRequested;rootFrame.NavigationFailed += OnNavigationFailed;if (e.PreviousExecutionState == ApplicationExecutionState.Terminated){//TODO: 从之前挂起的应用程序加载状态}// 将框架放在当前窗口中Window.Current.Content = rootFrame;}if (e.PrelaunchActivated == false){if (rootFrame.Content == null){// 当导航堆栈尚未还原时,导航到第一页,// 并通过将所需信息作为导航参数传入来配置// 参数rootFrame.Navigate(typeof(MainPage), e.Arguments);}// 确保当前窗口处于活动状态Window.Current.Activate();}}private void OnBackRequested(object sender, Windows.UI.Core.BackRequestedEventArgs e)
{Frame rootFrame = Window.Current.Content as Frame;if (rootFrame == null) return;if (rootFrame.CanGoBack && e.Handled == false){e.Handled = true;rootFrame.GoBack();}
}private void OnNavigated(object sender, NavigationEventArgs e)
{SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = ((Frame)sender).CanGoBack ?AppViewBackButtonVisibility.Visible :AppViewBackButtonVisibility.Collapsed;
}
3. UI自适应设计:
XAML代码如下:(利用VisualStateGroups、VisualState实现,使用Blend会更加方便;下方代码实现的是页面宽度在0-600px、600-800px和800px以上的视图变化)
<VisualStateManager.VisualStateGroups><VisualStateGroup x:Name="VisualStateGroup"><VisualStateGroup.Transitions><VisualTransition GeneratedDuration="0"/></VisualStateGroup.Transitions><VisualState x:Name="VisualStateMin0"><VisualState.Setters><Setter Target="left.(Grid.ColumnSpan)" Value="2" /><Setter Target="right.(UIElement.Visibility)" Value="Collapsed"/></VisualState.Setters><VisualState.StateTriggers><AdaptiveTrigger MinWindowWidth="1" /></VisualState.StateTriggers></VisualState><VisualState x:Name="VisualStateMin600"><VisualState.Setters><Setter Target="left.(Grid.ColumnSpan)" Value="2" /><Setter Target="right.(UIElement.Visibility)" Value="Collapsed"/></VisualState.Setters><VisualState.StateTriggers><AdaptiveTrigger MinWindowWidth="600" /></VisualState.StateTriggers></VisualState><VisualState x:Name="VisualStateMin800"><VisualState.Setters><Setter Target="right.(UIElement.Visibility)" Value="Visible"/></VisualState.Setters><VisualState.StateTriggers><AdaptiveTrigger MinWindowWidth="800" /></VisualState.StateTriggers></VisualState></VisualStateGroup>
</VisualStateManager.VisualStateGroups>
注意到上述代码并没有实现Image在Width<600px消失的功能,那是因为,在设计中,日程ListItem被定义为了一个DateTemplate,如果要对DateTemplate内的元素进行视觉状态调整,需要在DateTemplate内书写VisualStateGroups,并且使用UserControl标签嵌套。代码如下:
<ListView Grid.Row="1" IsItemClickEnabled="True" ItemClick="ListView_ItemClick" ItemsSource="{x:Bind ViewModel.AllItems}"><ListView.ItemTemplate><DataTemplate x:DataType="md:ListItem"><UserControl><Grid Height="100"><Grid.ColumnDefinitions><ColumnDefinition Width="42"/><ColumnDefinition Width="Auto"/><ColumnDefinition Width="*"/><ColumnDefinition Width="100"/></Grid.ColumnDefinitions><VisualStateManager.VisualStateGroups><VisualStateGroup x:Name="VisualStateGroup"><VisualState x:Name="VisualStateMin0"><VisualState.Setters><Setter Target="image.Visibility" Value="Collapsed" /></VisualState.Setters><VisualState.StateTriggers><AdaptiveTrigger MinWindowWidth="1" /></VisualState.StateTriggers></VisualState><VisualState x:Name="VisualStateMin600"><VisualState.Setters><Setter Target="image.Visibility" Value="Visible" /></VisualState.Setters><VisualState.StateTriggers><AdaptiveTrigger MinWindowWidth="600" /></VisualState.StateTriggers></VisualState></VisualStateGroup></VisualStateManager.VisualStateGroups>…… //此处省略了ListItem控件的代码</Grid></UserControl></DataTemplate></ListView.ItemTemplate>
</ListView>
4. 齿轮功能和删除按钮实现:
a) MainPage下主要实现CheckBoxClick(check,uncheck功能已经实现)、ListView_ItemClick、AddBarButtonClick、MenuFlyEdit_Click、MenuFlyDelete_Click五个绑定事件Click的函数(此后的代码会会经常用到ViewModel视图模板)。
private void ListView_ItemClick(object sender, ItemClickEventArgs e)
{ViewModel1.SelectedItem = e.ClickedItem as ListItem;if (right.Visibility.ToString() == "Collapsed"){Frame rootFrame = Window.Current.Content as Frame;rootFrame.Navigate(typeof(NewPage), e.ClickedItem as ListItem);}elseright.Navigate(typeof(NewPage), e.ClickedItem as ListItem);
}private void AddBarButtonClick(object sender, RoutedEventArgs e)
{if (right.Visibility.ToString() == "Collapsed"){Frame rootFrame = Window.Current.Content as Frame;rootFrame.Navigate(typeof(NewPage));}elseright.Navigate(typeof(NewPage));
}private void MenuFlyoutEdit_Click(object sender, RoutedEventArgs e) //齿轮Edit功能
{ViewModel1.SelectedItem = (sender as MenuFlyoutItem).DataContext as ListItem; //DateContext获取当前元素的上下文if (right.Visibility.ToString() == "Collapsed"){Frame rootFrame = Window.Current.Content as Frame;rootFrame.Navigate(typeof(NewPage), ViewModel.SelectedItem);}elseright.Navigate(typeof(NewPage), ViewModel.SelectedItem);
}private async void MenuFlyoutDelete_Click(object sender, RoutedEventArgs e) //齿轮Delete功能
{ViewModel1.SelectedItem = (sender as MenuFlyoutItem).DataContext as ListItem;ViewModel1.RemoveListItem(ViewModel1.SelectedItem);await new MessageDialog("Delete successfully!").ShowAsync();Frame rootFrame = Window.Current.Content as Frame;rootFrame.Navigate(typeof(MainPage));
}
b) NewPage 下主要实现Create_Update(之前已经实现)、Cancel、GetPicture(之前已经实现)、AddBarbuttonClick、DeleteBarButtonClick五个绑定函数的功能。
private void Cancel(object sender, RoutedEventArgs e)
{pic.Source = Item.Img;title.Text = Item.Title;slider.Value = Item.Size;detail.Text = Item.Detail;datePicker.Date = Item.Date;
}private async void DeleteBarButtonClick(object sender, RoutedEventArgs e)
{DeleteAppBarButton.Visibility = Visibility.Collapsed;MainPage.ViewModel1.RemoveListItem(MainPage.ViewModel1.SelectedItem);await new MessageDialog("Delete successfully!").ShowAsync();Frame rootFrame = Window.Current.Content as Frame;rootFrame.Navigate(typeof(MainPage));
}private void AddBarButtonClick(object sender, RoutedEventArgs e)
{if (SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility != AppViewBackButtonVisibility.Visible){Frame rootFrame = Window.Current.Content as Frame;if (Window.Current.Bounds.Width >= 800)rootFrame.Navigate(typeof(MainPage));elserootFrame.Navigate(typeof(NewPage));}
}
之所以两个cs文件中都有AddBarButtonClick函数的实现,是因为在程序启动之后,如果初始状态页面小于800px,则此时的AddAppBarButton为MainPage中实现的控件,点击AddAppBarButton,rootFrame导航的是NewPage,所以MainPage的AddAppBarButton已经不复存在则AddAppBarButton就变为NewPage中实现的控件;当页面大于800px,则此时的AddAppBarButton控件为NewPage中实现的,NewPage中AddAppBarButton控件将MainPage中的覆盖了。
删除按钮之所以不需要设置两个,是因为在任何可以点击删除按钮的时候,必定是你选中某一个日程的时候,这是Details页面一定会出现,即NewPage一定存在,所以只需要NewPage中有一个删除按钮即可。
除此之外,每次点击相应的ListItem,Details界面显示的是选中的ListItem的详情,所以我们需要重载OnNavigatedTo函数来实现这个功能,(此函数是每次跳转到NewPage后都会执行的函数(相对应的每次离开页面执行的函数为OnNavigatedFrom):
protected override void OnNavigatedTo(NavigationEventArgs e)
{if (e.Parameter != null){Item = e.Parameter as ListItem; //此处的Item即为前面所用到的中间量Item,用来记录当前ListItem信息DeleteAppBarButton.Visibility = Visibility.Visible;pic.Source = Item.Img;slider.Value = Item.Size;title.Text = Item.Title;detail.Text = Item.Detail;datePicker.Date = Item.Date;create_update.Content = "Update";}
}
5. 优化调整页面之间的导航跳转:
第二步中,我们已经实现了页面之间的跳转,但是你会发现,回到主页面的时候,有些情况下<-back回退按钮依然可以点击;还有+新建按钮也可以一直点击,造成页面越积越多,并且用户体验也会变差。
protected override void OnNavigatedTo(NavigationEventArgs e)
{SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Collapsed;
}
如上所示,在MainPage.xaml.cs中重载OnNavigatedTo函数,在该函数中将<-回退按钮隐藏,这样用户就可以知道这是主界面。
private void AddBarButtonClick(object sender, RoutedEventArgs e)
{if (SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility != AppViewBackButtonVisibility.Visible){Frame rootFrame = Window.Current.Content as Frame;if (Window.Current.Bounds.Width >= 800)rootFrame.Navigate(typeof(MainPage));elserootFrame.Navigate(typeof(NewPage));}
}
上面的if条件句即可实现 + 新建按钮不可以重复点击。(原因前文也提到了。)
实现这些函数需要理清楚每个函数所要处理的页面跳转,信息保留的关系,在此基础之上利用各种API完成这些内容。
以上便是这个日程制作的全部内容,如果有哪些不当的地方,还请大家批评指正。另外,附上所写源码链接:点击打开链接
这篇关于UWP--简单日程制作的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!