UWP开发之StreamSocket聊天室(四)

本节知识点:
  •     x:Bind 的使用
  •     ItemTemplateSelector 的使用


上篇我们完成了 UWP StreamSocket 客户端的所有ViewModel的编码工作,今天我们继续完成客户端的UI页面。


由于我们的业务逻辑都抽离到了VM里面,所以前端UI的逻辑代码基本没几行,来我们先看下设置界面的实现

一、ClientSetting界面

Pages/ClientSetting.xaml


<Page.BottomAppBar>
    <CommandBar Background="#00E6E6E6">
        <CommandBar.Content>
            <Grid/>
        </CommandBar.Content>
        <!--确认按钮-->
        <AppBarButton Icon="Accept" Label="确定" Click="{x:Bind _vm.ConnectionToServicer}"/>
    </CommandBar>
</Page.BottomAppBar>
 
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <StackPanel Margin="12">
        <!--配置区 Start-->
        <StackPanel Margin="0,6" Orientation="Horizontal">
            <TextBlock Text="服务器IP:"/>
            <TextBox Style="{StaticResource EditTextStyle}" Text="{x:Bind _vm.ServicerIp,Mode = TwoWay}" />
        </StackPanel>
        <StackPanel Margin="0,6" Orientation="Horizontal">
            <TextBlock Text="端 口  号:" />
            <TextBox Style="{StaticResource EditTextStyle}" Text="{x:Bind _vm.ServicerPort, Mode= TwoWay}" />
        </StackPanel>
        <StackPanel Margin="0,6" Orientation="Horizontal">
            <TextBlock Text="昵       称:" />
            <TextBox Style="{StaticResource EditTextStyle}" Text="{x:Bind _vm.UserModel.UserName, Mode= TwoWay}" />
        </StackPanel>
 
        <StackPanel Margin="0,6" Orientation="Horizontal">
            <TextBlock Text="连接状态:" />
            <TextBlock TextWrapping="WrapWholeWords" Text="{x:Bind _vm.SocketStateTxt, Mode = OneWay}"/>
        </StackPanel>
        <!--配置区End-->
    </StackPanel>
</Grid>


预览如下:



UI很简单,输入服务端配置信息以及自己的昵称即可,xaml中使用的是最新的x:Bind绑定,至于好处我就不赘述了……


Pages/ClientSetting.xaml.cs

public sealed partial class ClientSetting : Page
{
    private readonly SettingPageViewModel _vm = ViewModelLocator.Default.SettingPageViewModel;
 
    public ClientSetting()
    {
        InitializeComponent();
    }
}


后台代码很简单,就只需声明一个VM变量即可。

 
二、ClientMessage界面

ClientMessage界面更简单,一个ListView来显示消息列表,一个输入框填写消息,一个发送按钮 o(^▽^)o


……
xmlns:toolkit="using:SocketClientSample.Toolkit"
……
    <Page.Resources>
        <!--消息模板 - 别人发来的消息 Start -->
        <DataTemplate x:Key="OtherMsgDataTemplate" x:DataType="models: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>
        <!--End-->
 
        <!--消息模板 - 自己发出的消息 Start -->
        <DataTemplate x:Key="MyMsgDataTemplate"  x:DataType="models: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  Text="{x:Bind Message}" TextWrapping="Wrap"/>
                    </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>
        <!--End-->
    </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>
                 
                <!--模板选择器 Start-->
                <ListView.ItemTemplateSelector>
                    <toolkit:MsgStyleSelector MyMsgStyle="{StaticResource MyMsgDataTemplate}" OtherMsgStyle="{StaticResource OtherMsgDataTemplate}" />
                </ListView.ItemTemplateSelector>
                <!--End-->
            </ListView>
            <Grid Margin="0,8" Grid.Row="1">
                <StackPanel>
                    <TextBox Text="{x:Bind _vm.TextMsg,Mode=TwoWay}" KeyUp="{x:Bind _vm.MsgTextBoxKeyUp}" >
                    </TextBox>
                    <Button Margin="0,4"
                        Content="发送" HorizontalAlignment="Right" Click="{x:Bind _vm.SendMsg}" VerticalAlignment="Bottom"/>
                </StackPanel>
            </Grid>
        </Grid>
    </Grid>



运行后是这样子的:




上面的xaml代码中我们用到了项目模板选择器ItemTemplateSelector,用来使自己发送的消息与别人的消息显示为不同的样式,比如上面自己发送的消息是靠右的,而且自己发送的消息也不用显示昵称。

我们来看看这个模板选择器怎么使用的,新建一个Toolkit文件夹,在里面新建一个类文件名叫:MsgStyleSelector.cs编写以下代码:

public class MsgStyleSelector : DataTemplateSelector
{
    public DataTemplate OtherMsgStyle { get; set; }
 
    public DataTemplate MyMsgStyle { get; set; }
 
    protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
    {
        var msg = item as MessageModel;
        if (msg == null) return OtherMsgStyle;
        return msg.User == ViewModelLocator.Default.SettingPageViewModel.UserModel ? MyMsgStyle : OtherMsgStyle;
    }
}



上面我们继承DataTemplateSelector类,然后声明两个DataTemplate属性,一个用来存放 接收到的消息 的样式模板,另一个用来存放自己发送的消息的模板,然后重写父类SelectTemplateCore方法,该方法就是当有新的项被添加到集合并在UI上渲染时,就会通过该方法返回给新项要使用的样式。我们在该方法中判断新项是自己发送的消息还是别人发来的消息然后返回不同的模板就可以了。

界面上使用如下:
xmlns:toolkit="using:SocketClientSample.Toolkit"    
 
<ListView.ItemTemplateSelector>
    <toolkit:MsgStyleSelector MyMsgStyle="{StaticResource MyMsgDataTemplate}"
                              OtherMsgStyle="{StaticResource OtherMsgDataTemplate}" />
    </ListView.ItemTemplateSelector>



分别对MyMsgStyle、OtherMsgStyle属性赋值即可

类似的模板选择器还有其他的,比如:

  •     GroupStyleSelector:获取或设置对自定义 GroupStyleSelector 逻辑类的引用。GroupStyleSelector 返回其他 GroupStyle 值,以便用于基于内容特征的内容。
  •     ItemContainerStyleSelector:获取或设置对自定义 StyleSelector 逻辑类的引用。StyleSelector 返回不同的 Style 值以用于根据正在显示对象的特征的项容器。

大家可以在开发中酌情选择使用

接下来是ClientMessage的后台代码:

public sealed partial class ClientMessage : Page
{
    private MessagePageViewModel _vm = ViewModelLocator.Default.MessagePageViewModel;
 
    public ClientMessage()
    {
        InitializeComponent();
    }
    private async void SendedMsgAction(MessageModel obj)
    {
        await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {
            // 新消息到达后 滚动到新消息项
            MsgListView.ScrollIntoView(obj);
        });
    }
 
    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        base.OnNavigatedTo(e);
        //注册新消息到达的 Messeng
        Messenger.Default.Register<MessageModel>(this, "NewMsgAction", SendedMsgAction);
    }
 
    protected override void OnNavigatedFrom(NavigationEventArgs e)
    {
        base.OnNavigatedFrom(e);
        //取消注册的消息
        Messenger.Default.Unregister(this);
    }
}

 后台代码也没啥好说的,主要逻辑都在VM里,这里就仅仅注册了一个新消息到达的 Messeng,当新消息到达后让ListView滚动到新消息那一项,我们在VM中Send出去的这个NewMsgAction大家明白是怎么回事了吧,Messenger的作用就是VM 与 View之间的一个信使,当VM要处理View元素的时候就可以通过Send Messeng 告诉View 。


有些较真的童鞋该说了:"我有强迫症,使用了MvvM我就想把Page的后台逻辑都放到VM中。"然后把所有View上的UI元素都在VM中又映射了一份,各种痛苦。

其实这种想法是不科学的,我们把逻辑放到VM中是为了解耦,是为了项目以后的好维护 、好扩展、好移植,我们要根据具体的情况去判断把哪些逻辑放到VM中,比如上面这个情况,我就认为不应该放到VM中,为什么?因为让滚动条滚动到指定项本来就是View 的本质工作,控制View元素的行为本来就是属于View的职责,何必把它放到VM中呢?假设你真的放到VM中,日后有新的需求,比如不仅仅是滚动到最后一行,还要播放一个View的动画什么的,你还要把View动画元素再做份映射到VM中,假设以后你的项目要迁移到其他平台上,IoT模块上,上面根本就没有什么滚动条(假设)或者滚动条变了,你咋弄?改VM?所以该是谁的事情就让谁去做,至于沟通上的事情就放心的交给Messenger吧…

本文出自:53078485群大咖Aran
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值