• WPF+WCF一步一步打造音频聊天室(二):文字聊天和白板共享


        这篇文章将讲述实现WPF的UI和WCF中的双工通信。实现文字部分的聊天功能和实现共享白板的功能。

        画WPF的界面其实是一件麻烦的事情。虽然WPF和WindowsForm一样,能将控件拖到哪,它就在哪。我们在开发asp.net项目的时候用从原始的table布局,到现在流行的div+css布局。这些都需要设计人员的仔细设计。这个程序的布局我采用Grid和StackPanel两种方式。Gird类似html的表格布局,StackPanel就就像它的字面意思“堆栈面板”。

    WPF的UI实现

        首先新建一个wpf的应用程序,改名为ZqlChart。添加一个UserControl,用来实现登陆窗体,这个是用了StackPanel进行布局。XAML代码如下:

    <UserControl x:Class="ZqlChart.LoginControl"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Height="210" Width="350" Loaded="UserControl_Loaded">
        <StackPanel>
            <Border Height="220" BorderBrush="#FFFFFFFF" BorderThickness="2,2,2,0" CornerRadius="5,5,0,0">
                <Border.Background>
                    <LinearGradientBrush EndPoint="0.713,0.698" StartPoint="0.713,-0.139">
                        <GradientStop Color="#FFFFFFFF" Offset="0.933"/>
                        <GradientStop Color="LightBlue" Offset="0.337"/>
                    </LinearGradientBrush>
                </Border.Background>
                <StackPanel Name="infoPanel" Orientation="Vertical" Margin="10,10,10,10">
                    <StackPanel Name="typePanel" Orientation="Horizontal">
                        <RadioButton Name="chatTypeServer" FontSize="24" Margin="80,0,20,0" 
                                     Checked="chatTypeServer_Checked" VerticalContentAlignment="Center">服务端</RadioButton>
                        <RadioButton Name="chatTypeClient" FontSize="24" Checked="chatTypeClient_Checked" VerticalContentAlignment="Center">客户端</RadioButton>
                    </StackPanel>
                    <StackPanel Name="serverPanel" Orientation="Horizontal" Margin="0,10,0,0">
                        <Label Name="lblServer" FontSize="20" Width="120" HorizontalContentAlignment="Right" VerticalContentAlignment="Center">服务端:</Label>
                        <TextBox Height="30" Name="txtServer" Width="160" FontSize="20" VerticalContentAlignment="Center" />
                    </StackPanel>
                    <StackPanel Name="usernamePanel" Orientation="Horizontal" Margin="0,10,0,10">
                        <Label Name="lblUserName" FontSize="20" Width="120" HorizontalContentAlignment="Right">用户名:</Label>
                        <TextBox Height="30" Name="txtUserName" Width="160" FontSize="20" VerticalContentAlignment="Center" />
                    </StackPanel>
                    <StackPanel Name="buttonPanel" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
                        <Button Name="btnLogin" Width="120" FontSize="20" Margin="10,10,10,10" Click="btnLogin_Click">连接</Button>
                        <Button Name="btnCancel" Width="120" FontSize="20" Margin="10,10,10,10" Click="btnCancel_Click">取消</Button>
                    </StackPanel>
                </StackPanel>
            </Border>
    
        </StackPanel>
    </UserControl>

    界面效果如下:

    wpf3

    聊天的主界面,如下图:

    wpf4

    大框架是3行3列。XAML代码如下:

    <Window 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        x:Name="ZqlChartMainWindow"
     	x:Class="ZqlChart.ZqlChartWindow" 
        Title="麒麟语音聊天室” Height="600" Width="800"
        Background="#FF3B3737" Loaded="Window_Loaded" MinWidth="800" MinHeight="500">
        <Grid x:Name="LayoutRoot" >
            <Grid.RowDefinitions>
            
                <RowDefinition Height="50" />
                <RowDefinition Height="261" />
                <RowDefinition Height="250" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="150" />
                <ColumnDefinition Width="580*" />
                <ColumnDefinition Width="48" />
            </Grid.ColumnDefinitions>
    
            <Border Name="BorderUsersList" Grid.Column="0" Grid.Row="1" Grid.RowSpan="2" CornerRadius="8,8,8,8" Background="LightBlue" BorderThickness="0,0,4,4" >
                <ListView Name="lvUsers" Margin="10" FontSize="20">
                    <ListView.BitmapEffect>
                        <DropShadowBitmapEffect />
                    </ListView.BitmapEffect>
                </ListView>
            </Border>
    
            <Border Name="BorderEditingType" Grid.ColumnSpan="3" CornerRadius="8,8,8,8" Background="LightBlue" BorderThickness="0,4,4,4">
                    <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
                    <Button Margin="0,0,0,0"  Height="28" Width="121" Click="Button_Click" Background="White">
                      
                    </Button>
                    <RadioButton Name="rbInk" Content="墨水" Margin="15,0,0,0" VerticalAlignment="Center" FontSize="20" IsChecked="True" 
                                     Tag="{x:Static InkCanvasEditingMode.Ink}" Click="rbInkType_Checked">
                        </RadioButton>
                    <RadioButton Name="rbEraserByStroke" Content="一笔一笔清除" Margin="15,0,0,0" VerticalAlignment="Center" FontSize="20" 
                                     Tag="{x:Static InkCanvasEditingMode.EraseByStroke}" Click="rbInkType_Checked">
                        </RadioButton>
                    <RadioButton Name="rbEraserByPoint" Content="一点一点清除" Margin="15,0,0,0" VerticalAlignment="Center" FontSize="20" 
                                     Tag="{x:Static InkCanvasEditingMode.EraseByPoint}" Click="rbInkType_Checked">
                        </RadioButton>
                        <TextBlock Margin="25,0,10,0" VerticalAlignment="Center" FontSize="20" >:</TextBlock>
                        <Button Margin="0,0,0,0" Background="White" Height="28" Width="64" Click="OnSetFill">
                            <Rectangle Width="54" Height="20" Stroke="Black" StrokeThickness="2">
                                <Rectangle.Fill>
                                    <SolidColorBrush Color="{Binding ElementName=ZqlChartMainWindow, Path=FillColor}" />
                                </Rectangle.Fill>
                            </Rectangle>
                        </Button>
                        
                </StackPanel>
            </Border>
    
            <Border Name="BorderInkCanvas" Grid.Column="1" Grid.Row="1" Background="LightBlue" BorderThickness="4,4,4,4" CornerRadius="8,8,8,8" Grid.ColumnSpan="2">
                <InkCanvas x:Name="inkCanv" Margin="10" Background="White" 
                            StrokeCollected="inkCanv_StrokeCollected" StrokeErasing="inkCanv_StrokeErasing" 
                           StrokeErased="inkCanv_StrokeErased" VerticalAlignment="Top" >
                </InkCanvas>
            </Border>
    
            <Border Name="BorderInkMessage" Grid.Column="1" Grid.Row="2" Background="LightBlue" BorderThickness="0,0,4,4" CornerRadius="8,8,8,8" Grid.ColumnSpan="2">
                <Grid >
                    <Grid.RowDefinitions>
                        <RowDefinition />
                        <RowDefinition  Height="30" />
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="500*" />
                        <ColumnDefinition Width="62*" />
                        <ColumnDefinition Width="62*" />
                    </Grid.ColumnDefinitions>
                    <Border  Grid.Column="1" Grid.Row="1" Background="LightBlue" BorderThickness="4,4,4,4" CornerRadius="8,8,8,8" >
                        <Button Content="发送"  Height="23"  Name="btnSend"  Click="btnSend_Click" />
                    </Border>
                    <Border  Grid.ColumnSpan="3" Background="LightBlue" BorderThickness="4,4,4,4" CornerRadius="8,8,8,8" >
                        <TextBox   Name="txtAllMessage" >
                            <TextBox.BitmapEffect>
                                <DropShadowBitmapEffect />
                            </TextBox.BitmapEffect>
                        </TextBox>
                    </Border>
                    <Border  Grid.Row="1" Background="LightBlue" BorderThickness="4,4,4,4" CornerRadius="8,8,8,8" >
                        <TextBox Grid.Row="1" Name="txtMessage" />
                    </Border>
                    <Border  Grid.Column="2" Grid.Row="1"  Background="LightBlue" BorderThickness="4,4,4,4" CornerRadius="8,8,8,8" >
                        <Button  Content="关闭" Name="btnLeave"  Height="23" FontSize="10" Click="btnLeave_Click">
    
                        </Button>
                    </Border>
                </Grid>
            </Border>
            <Canvas Name="loginCanvas" Grid.Column="1" Grid.Row="1" Width="500" Height="300" VerticalAlignment="Top" HorizontalAlignment="Center" Margin="39,78,41,0" Grid.RowSpan="2" />
        </Grid>
    </Window>
    
    窗体就设计好了。
    WCF双工通信:

        双工通信能允许服务通知用户当前的进度情况。我们可以通过使用指定CallbackContract的ServiceContract属性的服务使用双工,如服务器端的代码如下:

    [ServiceContract(CallbackContract = typeof(IService1Callback))]
    public interface IService1
    {
        [OperationContract]
        string GetData(int value);
    }
     
    [ServiceContract]
    public interface IService1Callback
    {
        [OperationContract]
        void Reply(string message);
    }

        客户端代码:

    class Program
    {
        static void Main(string[] args)
        {
            var callback = new Service1Callback();
            var proxy = new Service1Client(new InstanceContext(callback));
            Console.WriteLine(proxy.GetData(42));
            Console.ReadLine();
        }
    }
     
    class Service1Callback : IService1Callback
    {
        public void Reply(string message)
        {
            Console.WriteLine(message);
        }
    }

        这篇文章中我将利用WCF的双工通信实现文字聊天的功能和共享白板的功能。

        定义协议:

        public interface IZqlChartService
        {
            [OperationContract()]
            bool Join(ChatUser chatUser);
    
            [OperationContract()]
            void Leave(ChatUser chatUser);
    
            [OperationContract]
            void SendBroadcastMessage(string strUserName, string message);
    
            [OperationContract()]
            bool IsUserNameTaken(string strUserName);
    
            [OperationContract()]
            void SendInkStrokes(MemoryStream memoryStream);
    
    
        }

    定义回调:

        public interface IZqlChartServiceCallback
        {
            [OperationContract(IsOneWay = true)]
            void NotifyMessage(string message);
    
            [OperationContract(IsOneWay = true)]
            void UpdateUsersList(List<ChatUser> listChatUsers);
    
            [OperationContract(IsOneWay = true)]
            void OnInkStrokesUpdate(ChatUser chatUser, byte[] bytesStroke);
    
            [OperationContract(IsOneWay = true)]
            void ServerDisconnected();
    
        }
    

    实现服务:

        public class ZqlChartService : IZqlChartService
        {
            public static Dictionary<IZqlChartServiceCallback, ChatUser> s_dictCallbackToUser = new Dictionary<IZqlChartServiceCallback, ChatUser>();
    
            public ZqlChartService()
            {
            }
    
            public bool Join(ChatUser chatUser)
            {
                IZqlChartServiceCallback client = OperationContext.Current.GetCallbackChannel<IZqlChartServiceCallback>();
    
                if (s_dictCallbackToUser.ContainsValue(chatUser) == false)
                {
                    s_dictCallbackToUser.Add(client, chatUser);
                }
    
                foreach (IZqlChartServiceCallback callbackClient in s_dictCallbackToUser.Keys)
                {
                    callbackClient.UpdateUsersList(s_dictCallbackToUser.Values.ToList());
                }
    
                return true;
            }
    
            public void Leave(ChatUser chatUser)
            {
                IZqlChartServiceCallback client = OperationContext.Current.GetCallbackChannel<IZqlChartServiceCallback>();
                if (s_dictCallbackToUser.ContainsKey(client))
                {
                    s_dictCallbackToUser.Remove(client);
                }
    
                foreach (IZqlChartServiceCallback callbackClient in s_dictCallbackToUser.Keys)
                {
                    if (chatUser.IsServer)
                    {
                        if (callbackClient != client)
                        {
                            //server user logout, disconnect clients
                            callbackClient.ServerDisconnected();
                        }
                    }
                    else
                    {
                        //normal user logout
                        callbackClient.UpdateUsersList(s_dictCallbackToUser.Values.ToList());
                    }
                }
    
                if (chatUser.IsServer)
                {
                    s_dictCallbackToUser.Clear();
                }
            }
    
            public bool IsUserNameTaken(string strNickName)
            {
                foreach (ChatUser chatUser in s_dictCallbackToUser.Values)
                {
                    if (chatUser.NickName.ToUpper().CompareTo(strNickName) == 0)
                    {
                        return true;
                    }
                }
                return false;
            }
    
            public void SendInkStrokes(MemoryStream memoryStream)
            {
                IZqlChartServiceCallback client = OperationContext.Current.GetCallbackChannel<IZqlChartServiceCallback>();
    
                foreach (IZqlChartServiceCallback callbackClient in s_dictCallbackToUser.Keys)
                {
                    if (callbackClient != OperationContext.Current.GetCallbackChannel<IZqlChartServiceCallback>())
                    {
                        callbackClient.OnInkStrokesUpdate(s_dictCallbackToUser[client], memoryStream.GetBuffer());
                    }
                }
            }
    
            public void SendBroadcastMessage(string clientName, string message)
            {
                IZqlChartServiceCallback client =
                    OperationContext.Current.GetCallbackChannel<IZqlChartServiceCallback>();
    
                if (client != null)
                {
    
                    foreach (IZqlChartServiceCallback callbackClient in s_dictCallbackToUser.Keys)
                    {
                        if (callbackClient != OperationContext.Current.GetCallbackChannel<IZqlChartServiceCallback>())
                        {
                            callbackClient.NotifyMessage(clientName + ": " + message);
                        }
                    }
                }
            }
    
        }

    客户端:

        [System.Diagnostics.DebuggerStepThroughAttribute()]
        [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
        public partial class ZqlChartServiceClient : System.ServiceModel.DuplexClientBase<IZqlChartService>, IZqlChartService
        {
    
            public ZqlChartServiceClient(System.ServiceModel.InstanceContext callbackInstance) :
                base(callbackInstance)
            {
            }
    
            public ZqlChartServiceClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName) :
                base(callbackInstance, endpointConfigurationName)
            {
            }
    
            public ZqlChartServiceClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName, string remoteAddress) :
                base(callbackInstance, endpointConfigurationName, remoteAddress)
            {
            }
    
            public ZqlChartServiceClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
                base(callbackInstance, endpointConfigurationName, remoteAddress)
            {
            }
    
            public ZqlChartServiceClient(System.ServiceModel.InstanceContext callbackInstance, System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
                base(callbackInstance, binding, remoteAddress)
            {
            }
    
            public bool Join(ZqlChartObjects.ChatUser chatUser)
            {
                return base.Channel.Join(chatUser);
            }
    
            public void Leave(ZqlChartObjects.ChatUser chatUser)
            {
                base.Channel.Leave(chatUser);
            }
    
            public bool IsUserNameTaken(string strUserName)
            {
                return base.Channel.IsUserNameTaken(strUserName);
            }
    
            public void SendInkStrokes(System.IO.MemoryStream memoryStream)
            {
                base.Channel.SendInkStrokes(memoryStream);
            }
    
            public void SendBroadcastMessage(string strUserName, string message)
            {
                base.Channel.SendBroadcastMessage(strUserName, message);
            }
    
    
    
        }

    客户端回调类:

        public class ClientCallBack : IZqlChartServiceCallback
        {
            public static ClientCallBack Instance;
            private SynchronizationContext m_uiSyncContext = null;
            private ZqlChartWindow m_mainWindow;
            //ActiveCallWindow _activeCallForm;
            //CallManager _callManager;
            public ClientCallBack(SynchronizationContext uiSyncContext, ZqlChartWindow mainWindow)
            {
                m_uiSyncContext = uiSyncContext;
                m_mainWindow = mainWindow;
            }
    
            public void OnInkStrokesUpdate(ZqlChartObjects.ChatUser chatUser, byte[] bytesStroke)
            {
                SendOrPostCallback callback =
                          delegate(object state)
                          {
                              m_mainWindow.OnInkStrokesUpdate(state as byte[] );
                          };
    
                m_uiSyncContext.Post(callback, bytesStroke);
    
                SendOrPostCallback callback2 =
                          delegate(object objchatUser)
                          {
                              m_mainWindow.LastUserDraw(objchatUser as ZqlChartObjects.ChatUser);
                          };
                m_uiSyncContext.Post(callback2, chatUser);
            }
    
            public void UpdateUsersList(List<ZqlChartObjects.ChatUser> listChatUsers)
            {
                SendOrPostCallback callback =
                         delegate(object objListChatUsers)
                         {
                             m_mainWindow.UpdateUsersList(objListChatUsers as List<ZqlChartObjects.ChatUser>);
                         };
    
                m_uiSyncContext.Post(callback, listChatUsers);
            }
    
            public void ServerDisconnected()
            {
                SendOrPostCallback callback =
                            delegate(object dummy)
                            {
                                m_mainWindow.ServerDisconnected();
                            };
    
                m_uiSyncContext.Post(callback, null);
            }
            public void NotifyMessage(string message)
            {
                SendOrPostCallback callback =
                                  delegate(object dummy)
                                  {
                                      m_mainWindow.NotifyMessage(message);
                                  };
    
                m_uiSyncContext.Post(callback, message);
            }
    
    
            public bool AcceptCall(string username)
            {
                //调獭?用?线?程ì必?须?为a STA,?因皑?为a许í多à UI 组哩?件t都?需è要癮。£
                return MessageBox.Show(String.Format("Accep call from \"{0}\" ", username), "Incomming Call", MessageBoxButton.YesNo, MessageBoxImage.Information) == MessageBoxResult.Yes;
            }
    
        }
    效果:
    1、服务端登陆:
    wfp5 
    2、客户端登录:
    wpf7 
    3、文字聊天
    wfp8 

    4、共享白板:

    wpf10

    总结:这篇文章实现了WPF的UI界面以及文字聊天和共享白板的功能。下一篇文章中将在此基础上实现语音通话的功能。

    参考文章:

    WXWinter的Silverlight例子_多人手写讨论板

    Tim Callaghan, Alvin LimDrawMeWCF

    附代码下载:https://files.cnblogs.com/zhuqil/ZqlChart.rar



    (全文完)


    以下为广告部分

    您部署的HTTPS网站安全吗?

    如果您想看下您的网站HTTPS部署的是否安全,花1分钟时间来 myssl.com 检测以下吧。让您的HTTPS网站变得更安全!

    SSL检测评估

    快速了解HTTPS网站安全情况。

    安全评级(A+、A、A-...)、行业合规检测、证书信息查看、证书链信息以及补完、服务器套件信息、证书兼容性检测等。

    SSL证书工具

    安装部署SSL证书变得更方便。

    SSL证书内容查看、SSL证书格式转换、CSR在线生成、SSL私钥加解密、CAA检测等。

    SSL漏洞检测

    让服务器远离SSL证书漏洞侵扰

    TLS ROBOT漏洞检测、心血漏洞检测、FREAK Attack漏洞检测、SSL Poodle漏洞检测、CCS注入漏洞检测。

  • 相关阅读:
    react 如何 阻止冒泡
    GitHub如何下载clone指定的tag
    git 建立一个私有模块
    如何获取域名(网址)对应的IP地址
    [翻译] JTCalendar
    将jsonModel转化为文件
    CALayer动画的暂停,恢复,以及结束时候的回调
    用标签页TitleSwitch切换不通的控制器
    设计可以多选的按钮ChooseManyButton
    设计标签选择器TitleSwitch
  • 原文地址:https://www.cnblogs.com/zhuqil/p/1752525.html
Copyright © 2020-2023  润新知