WPF一步步開發XMPP IM客戶端1:入門


[起因&目標]

因為工作原因接觸openfire服務端和spark客戶端開發,主要是基於openfire擴展開發了針對企業用途的服務器插件,還開發了各個平台上的客戶端(Windows\mac\android\ios\linux),詳情可搜索微信公眾號:CVTalk 

在開發過程中,發現基於Spark開發Java Swing客戶端(公司內部命名CVTalk)比較重,用戶體驗很難做到和微信客戶端看齊,皮膚的開發也比較費力,公司里絕大部分是windows的客戶端,而且高清分辨率屏幕的用戶越來越多, 那螞蟻一樣的小字和擁擠的布局改造很費勁,內存占用過多的問題也解決不了,swing客戶端越發力不從心。

於是,我打算利用業余時間做一個 輕量級、易用、穩定、美觀的Windows客戶端。並拋磚引玉放開源:https://lightchat.codeplex.com/

 

堅持輕客戶端,體驗第一。 目標功能只有7個

1、  單聊

2、  群聊

3、  人員搜索

4、  消息搜索

5、  截屏傳輸

6、  文件傳輸

7、  插件擴展平台和企業業務集成接口,及xmpp少量擴展和服務器插件

 

特性:

  • XMPP message、iq、presence、vcard、roster.....
  • MUC(Multi-User Chat) support
  • Fast localized(sqlite) user search,and Openfire ofUser import & sync
  • Easy message search console
  • Extensible organizational structure tree interface (Chinese users love it)
  • HD resolution screen support
  • ...

界面采用Modern UI。布局采用微信PC的方式,即搜索+聯系人+聊天窗一體,不開新窗。

第一版的界面,單聊界面

消息搜索:

 

[項目介紹]

項目目錄介紹:

 

 

主要類介紹

 

1、Config.cs:全局設置類

//是否顯示即時調試窗口
        public static bool IsDebug = false;
        public static String Version = "0.1";
//@加上bareJid的域名部分
        public static String Domain = "@im";
//注意:這個是域名,一般為該服務器的機器名
        public static String Server = "im";//外網或內網IP,目前AutoResolveConnectServer設置為true也不起作用,只能先改hosts文件
        //public static readonly String ServerIP = "111.63.127.83"; 
        public static readonly String ResourceName = "LightChat";
        public static readonly bool IsCheckPresence = false;
        public static readonly bool IsCheckChatState = false;
        public static readonly bool AutoAgents = false;
        public static readonly bool AutoPresence = true;
        public static readonly bool AutoRoster = true;

        //### 如果無法登陸,請在hosts文件中關聯IP到Server域名 ###//C:\Windows\System32\drivers\etc\hosts  最后一行增加IP映射 比如 111.63.127.83 im
        //自動解析connectserver屬性,設置為true就會解析server屬性即會利用System.Net.DNS.Resolve方法來將域名映射成ip地址
        public static readonly bool AutoResolveConnectServer = false;
        public static String MeCharacter = "我: ";

        //### Organization structure data source definition  ###
        //### 組織架構數據源定義 ###
        //support:XML\Json\SqliteDB
        //public static IDataSource OrgSource = new XmlDataSource();
        public static IDataSource OrgSource = new DbDataSource();

        //### User search data source ###
        //### 用戶搜索數據源 ###
        public static IResultsProvider SearchSource = new UserSearchProvider();

用戶搜索數據源,使用統一的返回結果接口IResultsProvider

public static IResultsProvider SearchSource = new UserSearchProvider();

 使用接口的目的是使業務代碼具有擴展性,比如組織架構數據源實現松耦合的具體實現。

2、登陸類: LightChat.Pages.Login

代碼

/// <summary>
    /// Login.xaml 的交互邏輯
    /// </summary>
    public partial class Login : UserControl
    {

        public Login()
        {
            InitializeComponent();
            //讀取設置
            this.Dispatcher.BeginInvoke(new Action(LoadSettings), null);            
        }

        private void btnLogin_Click(object sender, RoutedEventArgs e)
        {
            LoginTalk();
        }

        private void LoginTalk()
        {
            this.Dispatcher.BeginInvoke(new Action(BusyRun), null);

            if (tbxUsername.Text.Trim() == "")
            {
                ModernProgressRing mpr = new ModernProgressRing();                
                ModernDialog.ShowMessage("注意:您的賬戶名未填寫。", "提示",MessageBoxButton.OK);
                return;
            }
            if (tbxPassword.Password == "")
            {
                ModernDialog.ShowMessage("注意:您的賬戶名未填寫。", "提示", MessageBoxButton.OK);
                return;
            }

            LoginXMPP();
            SaveSettingsDB();
        }
        /// <summary>
        /// 執行登錄
        /// </summary>
        private void LoginXMPP()
        {
            XmppStatic.xmppCon = new XmppClientConnection();
            XmppStatic.xmppCon.Server = Config.Server;
            XmppStatic.xmppCon.Username = tbxUsername.Text.Trim();
            XmppStatic.xmppCon.Password = tbxPassword.Password;
            XmppStatic.xmppCon.Resource = Config.ResourceName;
            XmppStatic.xmppCon.Priority = 0;
            XmppStatic.xmppCon.AutoAgents = Config.AutoAgents;
            XmppStatic.xmppCon.AutoPresence = Config.AutoPresence;
            XmppStatic.xmppCon.AutoRoster = Config.AutoRoster;
            XmppStatic.xmppCon.AutoResolveConnectServer = Config.AutoResolveConnectServer;
            XmppStatic.xmppCon.UseStartTLS = true;
            XmppStatic.xmppCon.ClientVersion = "1.0";
            XmppStatic.xmppCon.Capabilities.Node = "http://www.cvtalk.cn/caps";
            SetDiscoInfo();

            try
            {
                //登錄事件,異步 
                XmppStatic.xmppCon.OnLogin += new ObjectHandler(xmppCon_OnLogin);
                //socket斷開事件
                XmppStatic.xmppCon.OnClose += new ObjectHandler(xmppCon_OnClose);
                //連接XMPP服務
                XmppStatic.xmppCon.Open();
                XmppStatic.SelfJID = new Jid(tbxUsername.Text.Trim() + Config.Domain +
                    "/"+ Config.ResourceName);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            
        }
    

        private void xmppCon_OnClose(object sender)
        {
            this.Dispatcher.BeginInvoke(new Action(UIChangeOnClose), null);
        }

        /// <summary>
        /// 斷線后的UI改變,需要后續開發斷線重連
        /// </summary>
        private void UIChangeOnClose()
        {
            UIStatic.mainHome.SetLogText("連接關閉");
            //todo : 自動重連邏輯...
        }
 
        /// <summary>
        /// 登錄后的UI改變
        /// </summary>
        /// <param name="sender"></param>
        void xmppCon_OnLogin(object sender)
        {
            this.Dispatcher.BeginInvoke(new Action(UIChangeOnLogin), null);
        }

        private void UIChangeBeforeLogin()
        {

        }
        private void UIChangeOnLogin()
        {
            tbxUsername.IsEnabled = false;
            tbxPassword.IsEnabled = false;
            btnLogin.IsEnabled = false;
            //設置主窗體的Tab展示
            UIStatic.mainWindow.SetBussTabText("Work"); 
            UIStatic.mainWindow.SetHomeTabText("Chat");
            UIStatic.mainWindow.SetHomeTabText();

            UIStatic.mainWindow.ContentSource = new System.Uri("/Pages/Home.xaml", UriKind.Relative);
            BusyStop();

            //VersionIq viq = new VersionIq(IqType.get, new Jid("wangxin@im/PC"), new Jid("zz@im"));
            //xmppCon.Send(viq);
            
            //DiscoManager dmg = new DiscoManager(xmppCon);
            //dmg.DiscoverInformation(new Jid("im"));

            //LastIq lastIq = new LastIq(IqType.get, new Jid("wangxin@im/PC"));
            //XmppStatic.xmppCon.Send(lastIq);

            PrivateIq privateIq = new PrivateIq(IqType.get);
            privateIq.Query.Storage = new agsXMPP.protocol.extensions.bookmarks.Storage();
            XmppStatic.xmppCon.IqGrabber.SendIq(privateIq, new IqCB(PrivateIqResult), null);

            ///SearchIq searchIq = new SearchIq(IqType.set, new Jid("search.im"));
            //searchIq.Query.Email = "wangx";
            //xmppCon.Send(searchIq);

            //VcardIq vcardIq = new VcardIq();
            //vcardIq.Type = IqType.get;
            //vcardIq.To = new Jid("wangxin@im");
            //XmppStatic.xmppCon.IqGrabber.SendIq(vcardIq, new IqCB(VcardIqResult), null);

            //BookmarkManager bkmg = new BookmarkManager(XmppStatic.xmppCon);
            //bkmg.RequestBookmarks();
        }

        private void VcardIqResult(object sender, IQ iq, object data)
        {
            if (iq.Type == IqType.result)
            {
                Vcard vcard = iq.Vcard;
                if (vcard != null)
                {
                    string fullname = vcard.Fullname;
                    string nickname = vcard.Nickname;
                    string description = vcard.Description;
                    Photo photo = vcard.Photo;
                }
            }
        }

        //獲取MUC會議室書簽Bookmarks
        private void PrivateIqResult(object sender, IQ iq, object data)
        {
            
            if (iq.Type == IqType.result)
            {
                Private piq = iq.Query as Private;
                if (piq != null)
                {
                    if (piq.HasChildElements)
                    {
                        XmppStatic.Conferences = ((Storage)piq.FirstChild).GetConferences();
                    }
                }
            }
        }

        /// <summary>
        /// 設置本地客戶端XMPP發現服務
        /// </summary>
        private void SetDiscoInfo()
        {
            XmppStatic.xmppCon.DiscoInfo.AddIdentity(new DiscoIdentity("pc", "cvtalk", "client"));

            XmppStatic.xmppCon.DiscoInfo.AddFeature(new DiscoFeature(agsXMPP.Uri.DISCO_INFO));
            XmppStatic.xmppCon.DiscoInfo.AddFeature(new DiscoFeature(agsXMPP.Uri.DISCO_ITEMS));
            XmppStatic.xmppCon.DiscoInfo.AddFeature(new DiscoFeature(agsXMPP.Uri.MUC));

            // for testing to bypass disco caches
            //_connection.DiscoInfo.AddFeature(new DiscoFeature(Guid.NewGuid().ToString())); 
        }

        private void LoadSettings()
        {
            //建立數據庫
            CreateSaveTable.InitDB();

            //讀取設置,用戶記住用戶名、密碼等
            DataTable dtSelect = LoadTable.LoadSettings();

            if (dtSelect == null || dtSelect.Rows.Count < 1) //沒有數據
            {
                chkRememberPSW.IsChecked = true;
            }
            else
            {
                tbxUsername.Text = dtSelect.Rows[0]["Jid"] + "";
                tbxPassword.Password = dtSelect.Rows[0]["Password"] + "";
                chkRememberPSW.IsChecked = dtSelect.Rows[0]["RememberPSW"] + "" == "true" ? true : false;
                chkAutomatic.IsChecked = dtSelect.Rows[0]["Automatic"] + "" == "true" ? true : false;
            }

        }

        /// <summary>
        /// 保存本地設置
        /// </summary>
        private void SaveSettingsDB()
        {
            CreateSaveTable.SaveSettingsDB(tbxUsername.Text, tbxPassword.Password, (bool)chkRememberPSW.IsChecked, (bool)chkAutomatic.IsChecked);
        }

        private void tbxPassword_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                //enter key is down
                LoginTalk();
            }
        }

        private void BusyRun()
        {
            //busyr.IsBusy = true;
            this._loading.Visibility = Visibility.Visible;
        }

        private void BusyStop()
        {
            //busyr.IsBusy = false;
            this._loading.Visibility = Visibility.Collapsed;
        }


    }

 

3、主窗體: LightChat.Pages.Home

區域介紹

 

 

代碼下載地址:https://lightchat.codeplex.com/

 

下一篇預告:WPF一步步開發XMPP IM客戶端:主窗體設計


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM