这篇文章是"UWP开发之StreamSocket聊天室"系列的最后一篇文章,这篇文章中我们来实现聊天室服务端View的实现。
由于很多View 、ViewModel和客户端的是基本一致的所以本篇内容会比较少,很多技术重合点这里也不会再做讲解。
其实在日常的开发中我们的服务端不应该是以UWP形式来开发的,通常情况下是在服务器使用Socket技术来搭建一个IM服务端,我们这里仅仅是为了探索StreamSocket Service在UWP上如何使用才如此去做。
首先我们还是先看设置界面的Xaml布局
SettingPage
Pages/SettingPage.xaml
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <StackPanel Margin="8"> <TextBlock> <Run Text="本机IP地址:"/> <Run Text="{x:Bind _vm.LocalHostName}"/> <LineBreak/> <Run Text="端口:"/> <Run Text="{x:Bind _vm.LocalServiceName}"/> </TextBlock> <ToggleSwitch x:Name="SocketSwitch" IsOn="{x:Bind _vm.SocketState,Mode = TwoWay}" Toggled="{x:Bind ListenSocket}" Header="开启服务" /> </StackPanel> </Grid>
放上一个TextBlock来显示本机IP和服务开放的端口号,然后用ToggleSwitch来控制服务的开启与关闭,其中ToggleSwitch控件的Toggled事件绑定到了后端的ListenSocket方法上。
来看看后端代码Pages/SettingPage.xaml.cs
public sealed partial class SettingPage : Page { private readonly SettingViewModel _vm = ViewModelLocator.Default.SettingViewModel; public SettingPage() { InitializeComponent(); } private void ListenSocket() { if (SocketSwitch.IsOn) { _vm.ServerSocket.Start(); } else { _vm.ServerSocket.Dispose(); } } }
后台代码也很简单,ListenSocket方法根据SocketSwitch的闭合去决定是开启服务还是关闭服务。开启和关闭服务的具体操作在该界面对应的SettingViewModel中。
看下该界面的ViewModel是什么样子的
ViewModel/SettingViewModel.cs
public class SettingViewModel : ViewModelBase { public UserModel UserModel { get; set; } = new UserModel {UserName = "服务器"}; /// <summary> /// Socket服务端 /// </summary> public SocketBase ServerSocket { get; set; } /// <summary> /// 监听状态文本描述 /// </summary> public string ListeningStateTxt { get; set; } /// <summary> /// 监听TCP链接的端口号 /// </summary> public string LocalServiceName { get; set; } /// <summary> /// 本地IP /// </summary> public string LocalHostName { get; set; } /// <summary> /// 是否已开启 Socket 服务 /// </summary> public bool SocketState { get; set; } /// <summary> /// 消息集合 /// </summary> public ObservableCollection<MessageModel> MessageCollection { get; set; } = new ObservableCollection<MessageModel>(); public SettingViewModel() { LocalHostName = GetLocalIp(); LocalServiceName = "22233"; //创建服务端Socket //(方法名忘记改了 就这样吧 CreatInkSocket 是创建Ink墨迹的服务端,前段时间做的Ink墨迹同步。大家如果看着不爽就自行改吧) ServerSocket = SocketFactory.CreatInkSocket(true, LocalHostName, LocalServiceName); //新消息到达通知 ServerSocket.MsgReceivedAction += data => { DispatcherHelper.CheckBeginInvokeOnUI(() => { MessageCollection.Add(data); }); Messenger.Default.Send(data, "NewMsgAction"); }; } /// <summary> /// 获取本地ip地址 /// </summary> /// <returns>ip</returns> private string GetLocalIp() { var icp = NetworkInformation.GetInternetConnectionProfile(); if (icp?.NetworkAdapter == null) return null; var hostname = NetworkInformation.GetHostNames() .SingleOrDefault( hn => hn.IPInformation?.NetworkAdapter != null && hn.IPInformation.NetworkAdapter.NetworkAdapterId == icp.NetworkAdapter.NetworkAdapterId); // the ip address return hostname?.CanonicalName; } }
Ok,配置界面的工作到此就完成了
MessagePage
来看看MessagePage的UI以及后台代码,和客户端的也是一毛一样的,还是贴一下代码吧,代码就不解释了,想要了解的可以看这篇文章: UWP开发之StreamSocket聊天室(四)
Pages/MessagePage.xaml
<Page.Resources> <DataTemplate x:Key="OtherMsgDataTemplate" x:DataType="model:MessageModel"> <Grid Margin="0,8"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Foreground="Red" VerticalAlignment="Center" > <Run Text="{x:Bind User.UserName}"/> <Run Text=": "/> <Run Text="{x:Bind SetDateTime}"/> </TextBlock> <Grid Grid.Row="1"> <Border Margin="24,0" Padding="16,4" Background="White" CornerRadius="12" HorizontalAlignment="Left" > <TextBlock TextWrapping="Wrap" Text="{x:Bind Message}"/> </Border> <Viewbox HorizontalAlignment="Left" Margin="16,0,0,0" Height="19" VerticalAlignment="Top" Width="13.5"> <Path Data="M32.4762,3.74901 C28.1542,4.60015 20.7241,2.92959 13.75,0.75 C15.5005,7.13589 28.4124,17.9116 29.5357,17.4874" Fill="White" Stretch="Fill" Stroke="White" UseLayoutRounding="False" d:LayoutOverrides="VerticalAlignment" /> </Viewbox> </Grid> </Grid> </DataTemplate> <DataTemplate x:Key="MyMsgDataTemplate" x:DataType="model:MessageModel"> <Grid Margin="0,8" HorizontalAlignment="Right"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Right" Foreground="Blue" > <Run/> <Run Text="{x:Bind SetDateTime}"/> </TextBlock> <Grid Grid.Row="1"> <Border Margin="24,0" Padding="16,4" Background="White" CornerRadius="12" HorizontalAlignment="Right" > <TextBlock TextWrapping="Wrap" Text="{x:Bind Message}"/> </Border> <Viewbox HorizontalAlignment="Right" Margin="16,0" Height="19" VerticalAlignment="Top" Width="13.5" RenderTransformOrigin="0.5,0.5"> <Viewbox.RenderTransform> <CompositeTransform ScaleX="-1"/> </Viewbox.RenderTransform> <Path Data="M32.4762,3.74901 C28.1542,4.60015 20.7241,2.92959 13.75,0.75 C15.5005,7.13589 28.4124,17.9116 29.5357,17.4874" Fill="White" Stretch="Fill" Stroke="White" UseLayoutRounding="False" d:LayoutOverrides="VerticalAlignment" /> </Viewbox> </Grid> </Grid> </DataTemplate> </Page.Resources> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <ListView x:Name="MsgListView" Background="#FFE6E6E6" ItemsSource="{x:Bind _vm.MessageCollection}" SelectionMode="None" > <ListView.ItemContainerStyle> <Style TargetType="ListViewItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch"/> </Style> </ListView.ItemContainerStyle> <ListView.ItemTemplateSelector> <toolkit:MsgStyleSelector MyMsgStyle="{StaticResource MyMsgDataTemplate}" OtherMsgStyle="{StaticResource OtherMsgDataTemplate}" /> </ListView.ItemTemplateSelector> </ListView> <Grid Margin="0,8" Grid.Row="1"> <StackPanel> <TextBox Text="{x:Bind _vm.TxtMsg,Mode= TwoWay}" KeyDown="{x:Bind _vm.MsgTextBoxKeyUp}" /> <Button Margin="0,4" Content="发送" HorizontalAlignment="Right" Click="{x:Bind _vm.SendTxtMsg}" VerticalAlignment="Bottom"/> </StackPanel> </Grid> </Grid> </Grid>
后台代码:
public sealed partial class MessagePage : Page { private MessageViewModel _vm = ViewModelLocator.Default.MessageViewModel; public MessagePage() { InitializeComponent(); } private async void SendedMsgAction(MessageModel obj) { await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { MsgListView.ScrollIntoView(obj); }); } protected override void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); Messenger.Default.Register<MessageModel>(this, "NewMsgAction", SendedMsgAction); } protected override void OnNavigatedFrom(NavigationEventArgs e) { base.OnNavigatedFrom(e); Messenger.Default.Unregister(this); } }
来看下该界面的ViewModel,ViewModel中的工作也和客户端的ViewModel工作一样:
public class MessageViewModel : ViewModelBase { private string _txtMsg; /// <summary> /// 要发送的文本 /// </summary> public string TxtMsg { get { return _txtMsg; } set { _txtMsg = value; RaisePropertyChanged(); } } /// <summary> /// 消息集合 /// </summary> public ObservableCollection<MessageModel> MessageCollection { get; } = ViewModelLocator.Default.SettingViewModel.MessageCollection; /// <summary> /// 发送消息 /// </summary> /// <returns></returns> public async Task SendTxtMsg() { if (string.IsNullOrEmpty(TxtMsg)) return; var msg = new MessageModel { MessageType = MessageType.TextMessage, Message = TxtMsg, SetDateTime = DateTime.Now, User = ViewModelLocator.Default.SettingViewModel.UserModel }; var socket = ViewModelLocator.Default.SettingViewModel.ServerSocket; await socket.SendMsg(msg); msg.Horizontal = HorizontalAlignment.Right; DispatcherHelper.CheckBeginInvokeOnUI(() => { MessageCollection.Add(msg); }); Messenger.Default.Send(msg, "NewMsgAction"); TxtMsg = null; } public async void MsgTextBoxKeyUp(object sender, KeyRoutedEventArgs key) { var textBox = sender as TextBox; if (textBox != null) TxtMsg = textBox.Text; if (key.Key != VirtualKey.Enter) return; if (string.IsNullOrEmpty(TxtMsg)) return; await SendTxtMsg(); } }
本篇中不再介绍ViewModel在ViewModelLocator.cs里面的注册,ViewModel的注册上篇已经介绍过,这里的和上篇一毛一样,传送门: UWP开发之StreamSocket聊天室(三)
至此该系列文章已全部讲述完毕,贴一下最终的效果图: