[起因&目標]
因為工作原因接觸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客戶端:主窗體設計