WPF在自定義文本框中實(shí)現(xiàn)輸入法跟隨光標(biāo)
本文告訴大家在 WPF 寫一個(gè)自定義的文本框,如何實(shí)現(xiàn)讓輸入法跟隨光標(biāo)
本文非小白向,本文適合想開(kāi)發(fā)自定義的文本框,從底層開(kāi)始開(kāi)發(fā)的文本庫(kù)的伙伴。在開(kāi)始之前,期望了解了文本庫(kù)開(kāi)發(fā)的基礎(chǔ)知識(shí)
本文實(shí)現(xiàn)的效果如下
實(shí)現(xiàn)
本文的方法參考了 WPF 官方倉(cāng)庫(kù)的邏輯,可以在WPF倉(cāng)庫(kù)的wpf\src\Microsoft.DotNet.Wpf\src\PresentationFramework\System\Windows\Documents\ImmComposition.cs
文件看到官方是如何讓TextBox控件獲取輸入法焦點(diǎn),和在輸入光標(biāo)變更時(shí),修改輸入法的輸入框坐標(biāo)
先了解一下輸入法的相關(guān)知識(shí)。在 Windows 編程開(kāi)發(fā)里,輸入法框架有三套,其中用的最多的是第二套。第二套是采用 IMM 進(jìn)行對(duì)接的。所謂 IMM 就是 Input Method Manager 也就是 輸入法管理器
相關(guān)的另一個(gè)縮寫詞 IME 則是 Input Method Editor 或者是 Input Method Engine 的縮寫,含義是輸入法編輯器或輸入法引擎
應(yīng)用程序可以通過(guò) IMM 對(duì)接輸入法。所用的 win32 的 API 重點(diǎn)是如下幾個(gè)
- ImmGetContext 獲取輸入法上下文,用于后續(xù)所有的其他函數(shù)調(diào)用
- ImmAssociateContext 關(guān)聯(lián)輸入法和對(duì)應(yīng)的窗口,讓輸入法了解在哪個(gè)窗口輸入
- ImmSetCompositionWindow 用來(lái)設(shè)置輸入法的窗口的坐標(biāo),也是本文最重要的函數(shù)
本文接下來(lái)將告訴大家如何一步步實(shí)現(xiàn)封裝對(duì) IME 輸入法調(diào)用,在本文最后將會(huì)給出所有的源代碼
這部分對(duì)輸入法的邏輯可以封裝為一個(gè)類,這樣上層就可以不關(guān)注細(xì)節(jié)邏輯。如例子代碼,放在 IMESupporter 類型里
為了方便文本框的接入,咱再定義一個(gè)接口,用于設(shè)置文本框需要實(shí)現(xiàn)一些方法,用來(lái)提供參數(shù)給 IMESupporter 使用才能進(jìn)行接入
/// <summary> /// 表示控件支持被輸入法 /// </summary> interface IIMETextEditor { /// <summary> /// 獲取當(dāng)前使用的字體名 /// </summary> /// <returns></returns> string GetFontFamilyName(); /// <summary> /// 獲取字號(hào)大小,單位和 WPF 的 FontSize 相同 /// </summary> /// <returns></returns> int GetFontSize(); /// <summary> /// 獲取輸入框的左上角的點(diǎn),用于設(shè)置輸入法的左上角。此點(diǎn)相對(duì)于 <see cref="IIMETextEditor"/> 所在元素坐標(biāo)。對(duì)大部分控件來(lái)說(shuō),都應(yīng)該是 0,0 點(diǎn) /// </summary> /// <returns></returns> Point GetTextEditorLeftTop(); /// <summary> /// 獲取光標(biāo)的輸入左上角的點(diǎn)。此點(diǎn)相對(duì)于 <see cref="IIMETextEditor"/> 所在元素坐標(biāo) /// </summary> /// <returns></returns> Point GetCaretLeftTop(); }
對(duì)于如微軟拼音等輸入法,是支持設(shè)置輸入法的文本大小和字體。因此就需要文本框提供 GetFontFamilyName 和 GetFontSize 方法
而 GetCaretLeftTop 自然就是用來(lái)讓輸入法跟隨的。為了讓文本框可以做更多的定制,也需要 GetTextEditorLeftTop 方法,這個(gè)方法的返回值對(duì)大部分自定義的文本框控件來(lái)說(shuō),都應(yīng)該是 0,0 點(diǎn)
在 IMESupporter 類型構(gòu)造函數(shù),期望傳入文本框控件,如此可以解決初始化值和監(jiān)聽(tīng)的鍋
internal class IMESupporter<T> where T : UIElement, IIMETextEditor { // ReSharper disable InconsistentNaming public IMESupporter(T editor) { Editor = editor; // 忽略代碼 } }
為了同時(shí)約束傳入的文本框控件繼承 UIElement 和 IIMETextEditor 接口,用了泛形
在文本框控件 Editor 獲取焦點(diǎn)的時(shí)候,將需要喚起輸入法進(jìn)行輸入。在 Editor 失去焦點(diǎn)的時(shí)候,就應(yīng)該告訴輸入法當(dāng)前不進(jìn)行輸入
public IMESupporter(T editor) { Editor = editor; Editor.GotKeyboardFocus += Editor_GotKeyboardFocus; Editor.LostKeyboardFocus += Editor_LostKeyboardFocus; } private T Editor { get; }
根據(jù) WPF 的約定,對(duì)自定義的支持輸入法的控件,需要設(shè)置 IsInputMethodSuspendedProperty 附加屬性,如下面代碼
InputMethod.SetIsInputMethodSuspended(editor, true);
在 Editor_GotKeyboardFocus
需要實(shí)現(xiàn)的邏輯是調(diào)起輸入法和設(shè)置初始的輸入框的坐標(biāo)。如上文,開(kāi)始之前,需要先拿到輸入法上下文。在拿到輸入法上下文之前,可以先獲取默認(rèn)的 IME 類窗口句柄。先獲取默認(rèn)的 IME 類窗口句柄是為了在多進(jìn)程嵌入窗口時(shí),讓微軟拼音輸入法的輸入框跟隨輸入光標(biāo)而不是在左上角
_defaultImeWnd = IMENative.ImmGetDefaultIMEWnd(IntPtr.Zero);
以上的 _defaultImeWnd
是一個(gè)字段,在 IMESupporter 里定義如下字段和屬性
private T Editor { get; } private IntPtr _defaultImeWnd; private IntPtr _currentContext; private IntPtr _previousContext; private HwndSource? _hwndSource; private bool _isUpdatingCompositionWindow;
這里有一個(gè)細(xì)節(jié)是 ImmGetDefaultIMEWnd 也許會(huì)返回 0x00 空值。什么時(shí)候會(huì)返回空值?如打開(kāi)一個(gè) Win32Dialog 窗口,如 OpenFileDialog 或 SaveFileDialog 等,之后關(guān)閉,那么此時(shí)也許 ImmGetDefaultIMEWnd 將會(huì)返回空值
拿到空值,需要重新綁定輸入法,告訴輸入法當(dāng)前的窗口獲取輸入焦點(diǎn),可以使用如下代碼,通過(guò)修改附加屬性的值,通過(guò)附加屬性變更調(diào)用到 WPF 框架的邏輯,從而修復(fù)此問(wèn)題
if (_defaultImeWnd == IntPtr.Zero) { // 如果拿到了空的默認(rèn) IME 窗口了,那么此時(shí)也許是作為嵌套窗口放入到另一個(gè)進(jìn)程的窗口 // 拿不到就需要刷新一下。否則微軟拼音輸入法將在屏幕的左上角上 RefreshInputMethodEditors(); // 忽略代碼 } /// <summary> /// 刷新 IME 的 ITfThreadMgr 狀態(tài),用于修復(fù)打開(kāi) Win32Dialog 之后關(guān)閉,輸入法無(wú)法輸入中文問(wèn)題 /// </summary> /// 原因是在打開(kāi) Win32Dialog 之后,將會(huì)讓 ITfThreadMgr 失去焦點(diǎn)。因此需要使用本方法刷新,通過(guò) InputMethod 的 IsInputMethodEnabledProperty 屬性調(diào)用到 InputMethod 的 EnableOrDisableInputMethod 方法,在這里面調(diào)用到 TextServicesContext.DispatcherCurrent.SetFocusOnDefaultTextStore 方法,從而調(diào)用到 SetFocusOnDim(DefaultTextStore.Current.DocumentManager) 的代碼,將 DefaultTextStore.Current.DocumentManager 設(shè)置為 ITfThreadMgr 的焦點(diǎn),重新綁定 IME 輸入法 /// 但是即使如此,依然拿不到 <see cref="_defaultImeWnd"/> 的初始值。依然需要重新打開(kāi)和關(guān)閉 WPF 窗口才能拿到 /// [Can we public the `DefaultTextStore.Current.DocumentManager` property to create custom TextEditor with IME · Issue #6139 · dotnet/wpf](https://github.com/dotnet/wpf/issues/6139 ) private void RefreshInputMethodEditors() { if (InputMethod.GetIsInputMethodEnabled(Editor)) { InputMethod.SetIsInputMethodEnabled(Editor, false); } if (InputMethod.GetIsInputMethodSuspended(Editor)) { InputMethod.SetIsInputMethodSuspended(Editor, false); } InputMethod.SetIsInputMethodEnabled(Editor, true); InputMethod.SetIsInputMethodSuspended(Editor, true); }
除了給 ImmGetDefaultIMEWnd 傳入 IntPtr.Zero 可以獲取之外,還可以傳入當(dāng)前的 Editor 所在的 HwndSource
進(jìn)行獲取,這里的 HwndSource 就相當(dāng)于或者說(shuō)大多數(shù)時(shí)候是等于 Editor 所在的窗口
_hwndSource = (HwndSource) (PresentationSource.FromVisual(Editor) ?? throw new ArgumentNullException(nameof(Editor))); if (_defaultImeWnd == IntPtr.Zero) { // 如果拿到了空的默認(rèn) IME 窗口了,那么此時(shí)也許是作為嵌套窗口放入到另一個(gè)進(jìn)程的窗口 // 拿不到就需要刷新一下。否則微軟拼音輸入法將在屏幕的左上角上 RefreshInputMethodEditors(); // 嘗試通過(guò) _hwndSource 也就是文本所在的窗口去獲取 _defaultImeWnd = IMENative.ImmGetDefaultIMEWnd(_hwndSource.Handle); // 忽略代碼 }
如果繼續(xù)獲取不到,那么可以嘗試使用 GetForegroundWindow 獲取。使用 GetForegroundWindow 獲取到的也許不是正確的,但是能進(jìn)入此分支,也好過(guò)沒(méi)有輸入法
_defaultImeWnd = IMENative.ImmGetDefaultIMEWnd(_hwndSource.Handle); if (_defaultImeWnd == IntPtr.Zero) { // 如果依然獲取不到,那么使用當(dāng)前激活的窗口,在準(zhǔn)備輸入的時(shí)候 // 當(dāng)前的窗口大部分都是對(duì)的 // 進(jìn)入這里,是盡可能恢復(fù)輸入法,拿到的 GetForegroundWindow 雖然預(yù)計(jì)是不對(duì)的 // 也好過(guò)沒(méi)有輸入法 _defaultImeWnd = IMENative.ImmGetDefaultIMEWnd(Win32.User32.GetForegroundWindow()); }
接下來(lái)通過(guò) _defaultImeWnd
獲取輸入法上下文,如下面代碼
// 使用 DefaultIMEWnd 可以比較好解決微軟拼音的輸入法到屏幕左上角的問(wèn)題 _currentContext = IMENative.ImmGetContext(_defaultImeWnd);
如果從 _defaultImeWnd
拿不到,則使用 _hwndSource.Handle
獲取
_currentContext = IMENative.ImmGetContext(_defaultImeWnd); if (_currentContext == IntPtr.Zero) { _currentContext = IMENative.ImmGetContext(_hwndSource.Handle); }
獲取上下文之后,將輸入法上下文和當(dāng)前窗口關(guān)聯(lián)起來(lái)。對(duì)于只實(shí)現(xiàn)第二套輸入法框架的輸入法,應(yīng)用程序調(diào)用 ImmAssociateContext 關(guān)聯(lián),即可調(diào)起此輸入法在關(guān)聯(lián)的窗口輸入
// 對(duì) Win32 使用第二套輸入法框架的輸入法,可以采用 ImmAssociateContext 關(guān)聯(lián) // 但是對(duì)實(shí)現(xiàn) TSF 第三套輸入法框架的輸入法,在應(yīng)用程序?qū)拥谌纵斎敕蚣? // 就需要調(diào)用 ITfThreadMgr 的 SetFocus 方法。剛好 WPF 對(duì)接了 _previousContext = IMENative.ImmAssociateContext(_hwndSource.Handle, _currentContext);
輸入法在輸入過(guò)程中,將會(huì)通過(guò) Windows 消息和當(dāng)前窗口進(jìn)行通訊,如獲取輸入框所需的坐標(biāo)和輸入文本等。因此咱需要加上 Hook 消息,用于告訴輸入法坐標(biāo)。但不需要處理輸入的文本的邏輯,因?yàn)檩斎胛谋镜倪壿嫷仍?WPF 已有處理
_previousContext = IMENative.ImmAssociateContext(_hwndSource.Handle, _currentContext); _hwndSource.AddHook(WndProc);
關(guān)于 WndProc 的函數(shù)邏輯,咱放在后面
在 WPF 框架里,會(huì)對(duì)第三套輸入法有進(jìn)行支持,于是就需要調(diào)用 ITfThreadMgr
這個(gè) COM 組件進(jìn)行關(guān)聯(lián)焦點(diǎn),如下面代碼
// 盡管文檔說(shuō)傳遞null是無(wú)效的,但這似乎有助于在與WPF共享的默認(rèn)輸入上下文中激活I(lǐng)ME輸入法 // 這里需要了解的是,在 WPF 的邏輯,是需要傳入 DefaultTextStore.Current.DocumentManager 才符合預(yù)期 IMENative.ITfThreadMgr? threadMgr = IMENative.GetTextFrameworkThreadManager(); threadMgr?.SetFocus(IntPtr.Zero);
初始化的過(guò)程還需要給輸入法的輸入框一個(gè)初始化的坐標(biāo),可使用 Win32 的 ImmSetCompositionWindow 進(jìn)行設(shè)置。在進(jìn)行設(shè)置之前,需要獲取到文本框的輸入光標(biāo)相對(duì)于窗口的坐標(biāo),用于給輸入法使用
下面代碼從文本框獲取文本框?qū)崿F(xiàn)接口的獲取光標(biāo)和輸入框左上角
var textEditorLeftTop = Editor.GetTextEditorLeftTop(); var caretLeftTop = Editor.GetCaretLeftTop();
接下來(lái)使用如下代碼將坐標(biāo)轉(zhuǎn)換為相對(duì)于窗口的
var hIMC = _currentContext; HwndSource source = _hwndSource; var textEditorLeftTop = Editor.GetTextEditorLeftTop(); var caretLeftTop = Editor.GetCaretLeftTop(); var transformToAncestor = Editor.TransformToAncestor(source.RootVisual); var textEditorLeftTopForRootVisual = transformToAncestor.Transform(textEditorLeftTop); var caretLeftTopForRootVisual = transformToAncestor.Transform(caretLeftTop);
對(duì) surface 設(shè)備來(lái)說(shuō),需要進(jìn)行更多的處理
//解決surface上輸入法光標(biāo)位置不正確 //現(xiàn)象是surface上光標(biāo)的位置需要乘以2才能正確,普通電腦上沒(méi)有這個(gè)問(wèn)題 //且此問(wèn)題與DPI無(wú)關(guān),目前用CaretWidth可以有效判斷 caretLeftTopForRootVisual = new Point(caretLeftTopForRootVisual.X / SystemParameters.CaretWidth, caretLeftTopForRootVisual.Y / SystemParameters.CaretWidth);
獲取到的坐標(biāo)傳入到 ImmSetCompositionWindow 方法
//const int CFS_DEFAULT = 0x0000; //const int CFS_RECT = 0x0001; const int CFS_POINT = 0x0002; //const int CFS_FORCE_POSITION = 0x0020; //const int CFS_EXCLUDE = 0x0080; //const int CFS_CANDIDATEPOS = 0x0040; var form = new IMENative.CompositionForm(); form.dwStyle = CFS_POINT; form.ptCurrentPos.x = (int) Math.Max(caretLeftTopForRootVisual.X, textEditorLeftTopForRootVisual.X); form.ptCurrentPos.y = (int) Math.Max(caretLeftTopForRootVisual.Y, textEditorLeftTopForRootVisual.Y); //if (_isSoftwarePinYinOverWin7) //{ // form.ptCurrentPos.y += (int) characterBounds.Height; //} IMENative.ImmSetCompositionWindow(hIMC, ref form);
以上注釋的 _isSoftwarePinYinOverWin7
的邏輯是判斷在系統(tǒng)版本大于 Win7 的系統(tǒng),如 Win10 系統(tǒng)上,使用微軟拼音輸入法,微軟拼音輸入法在幾個(gè)版本,需要修改 Y 坐標(biāo),加上輸入的行高才可以。但是在一些 Win10 版本,通過(guò)補(bǔ)丁又修了這個(gè)問(wèn)題
以上就完成了輸入法的初始化邏輯
接下來(lái)就是需要處理 Windows 消息了,如在收到 WM_INPUTLANGCHANGE
消息時(shí),需要重新獲取輸入法上下文
private IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { switch (msg) { // 忽略代碼 case IMENative.WM_INPUTLANGCHANGE: if (_hwndSource != null) { CreateContext(); } // 忽略代碼 break; } return IntPtr.Zero; }
以上獲取輸入法上下文 CreateContext 方法是獲取 _currentContext
的邏輯
在收到 WM_IME_COMPOSITION
消息,需要更新輸入法的輸入框的坐標(biāo)
private IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { switch (msg) { // 忽略代碼 case IMENative.WM_IME_COMPOSITION: UpdateCompositionWindow(); break; // 忽略代碼 } return IntPtr.Zero; }
以上的 UpdateCompositionWindow 方法是調(diào)用 ImmSetCompositionWindow 方法設(shè)置坐標(biāo)的方法
關(guān)于此 IMESupporter 類型的所有代碼,可以從下文獲取
接下來(lái)是對(duì)接 IMESupporter 和具體的文本框
先在自定義的文本框 TextEditor 控件上繼承 IIMETextEditor 接口。為了方便調(diào)試,咱先寫測(cè)試邏輯,獲取的輸入光標(biāo)就是上次鼠標(biāo)點(diǎn)擊的點(diǎn)以及固定的字體字號(hào)
public partial class TextEditor : FrameworkElement, IIMETextEditor { // 忽略代碼 protected override void OnRender(DrawingContext drawingContext) { drawingContext.DrawRectangle(Brushes.Black,null,new Rect(MouseDownPoint,new Size(3,30))); base.OnRender(drawingContext); } protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) { // 讓控件接收點(diǎn)擊 return new PointHitTestResult(this, hitTestParameters.HitPoint); } protected override void OnMouseDown(MouseButtonEventArgs e) { MouseDownPoint = e.GetPosition(this); Focus(); InvalidateVisual(); } private Point MouseDownPoint { get; set; } string IIMETextEditor.GetFontFamilyName() { return "微軟雅黑"; } int IIMETextEditor.GetFontSize() { return 30; } Point IIMETextEditor.GetTextEditorLeftTop() { // 相對(duì)于當(dāng)前輸入框的坐標(biāo) return new Point(0, 0); } Point IIMETextEditor.GetCaretLeftTop() { return MouseDownPoint; } }
在 OnMouseDown 方法里面,需要調(diào)用 Focus 獲取焦點(diǎn),同時(shí)更新一下模擬的光標(biāo)。模擬的光標(biāo)是在 OnRender 方法里面,使用畫出一個(gè)矩形模擬的,沒(méi)有做閃爍
為了讓控件能接收鍵盤消息,需要設(shè)置 FocusableProperty 屬性。為了接收 Tab 鍵,而不是被切到其他控件,需要設(shè)置 KeyboardNavigation 的 IsTabStopProperty 和 TabNavigationProperty 附加屬性。因?yàn)檫@是作用在所有的自定義文本框 TextEditor 控件上的,因此可以在 TextEditor 的靜態(tài)構(gòu)造函數(shù),進(jìn)行更改默認(rèn)值,代碼如下
static TextEditor() { // 用于接收 Tab 按鍵,而不是被切換焦點(diǎn) KeyboardNavigation.IsTabStopProperty.OverrideMetadata(typeof(TextEditor), new FrameworkPropertyMetadata(true)); KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(TextEditor), new FrameworkPropertyMetadata(KeyboardNavigationMode.None)); // 用于獲取焦點(diǎn)邏輯 FocusableProperty.OverrideMetadata(typeof(TextEditor), new FrameworkPropertyMetadata(true)); }
完成 TextEditor 控件的配置,就可以對(duì)接 IMESupporter 類,對(duì)接方法是創(chuàng)建即可
public TextEditor() { // 忽略代碼 _imeSupporter = new IMESupporter<TextEditor>(this); } private readonly IMESupporter<TextEditor> _imeSupporter;
這樣就完成了文本框讓輸入法跟隨輸入的功能
代碼
本文所有代碼放在github 和 gitee 歡迎訪問(wèn)
可以通過(guò)如下方式獲取本文的源代碼,先創(chuàng)建一個(gè)空文件夾,接著使用命令行 cd 命令進(jìn)入此空文件夾,在命令行里面輸入以下代碼,即可獲取到本文的代碼
git init git remote add origin https://gitee.com/lindexi/lindexi_gd.git git pull origin b3a1fffece8284d0b84407aa13d949de6a2f1536
以上使用的是 gitee 的源,如果 gitee 不能訪問(wèn),請(qǐng)?zhí)鎿Q為 github 的源
git remote remove origin git remote add origin https://github.com/lindexi/lindexi_gd.git
獲取代碼之后,打開(kāi) LightTextEditorPlus.sln 文件
到此這篇關(guān)于WPF在自定義文本框中實(shí)現(xiàn)輸入法跟隨光標(biāo)的文章就介紹到這了,更多相關(guān)WPF輸入法跟隨光標(biāo)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
ASP.NET?Core框架探索之Authentication的權(quán)限認(rèn)證過(guò)程解析
這篇文章主要介紹了ASP.NET?Core框架探索之Authentication的相關(guān)知識(shí),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03.NET Core/Framework如何創(chuàng)建委托大幅度提高反射調(diào)用的性能詳解
反射是一種很重要的技術(shù),下面這篇文章主要給大家介紹了關(guān)于.NET Core/Framework如何創(chuàng)建委托大幅度提高反射調(diào)用性能的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2018-09-09Asp.net 文件上傳類(取得文件后綴名,保存文件,加入文字水印)
Asp.net 取得文件后綴名,保存文件,加入文字水印的代碼類2008-11-11ASP.NET動(dòng)態(tài)生成靜態(tài)頁(yè)面的實(shí)例代碼
生成靜態(tài)頁(yè)有很多好處,可以緩解服務(wù)器壓力、方便搜索網(wǎng)站搜索等等,下面介紹一下生成靜態(tài)頁(yè)的實(shí)例代碼,有需要的朋友可以參考一下2013-07-07Asp.Net二級(jí)域名共享Forms身份驗(yàn)證、下載站/圖片站的授權(quán)訪問(wèn)控制
我們平時(shí)一般在做圖片或者文件下載權(quán)限控制的時(shí)候基本都是控制到下載頁(yè)面的,當(dāng)你的下載地址暴露后,瀏覽者就直接可以通過(guò)文件地址進(jìn)行下載了,這時(shí)候也就出現(xiàn)了我們常說(shuō)的盜鏈2012-02-02C#反射技術(shù)的簡(jiǎn)單操作(讀取和設(shè)置類的屬性)
反射的作用想必大家都知道了吧,少量屬性的自動(dòng)化操作手動(dòng)添加幾下當(dāng)然是沒(méi)有問(wèn)題的,但是屬性數(shù)量較多的時(shí)候敲起這些繁鎖的代碼可以困了,再說(shuō)對(duì)擴(kuò)展和維護(hù)性造成很多的不遍,以下代碼中如不能直接使用請(qǐng)?zhí)砑觰sing System.Text;的引用。2011-01-01asp.net(c#)程序版本升級(jí)更新的實(shí)現(xiàn)代碼
我們做了程序,不免會(huì)有版本升級(jí),這就需要程序有自動(dòng)版本升級(jí)的功能。那么看看我是如何實(shí)現(xiàn)程序自動(dòng)更新的。2010-03-03asp.net UpdatePanel的簡(jiǎn)單用法
局部更新是ajax技術(shù)的最基本,也是最重要的用法,今天大概把a(bǔ)sp.net ajax中的局部更新控件 updatepanel的用法記錄下,大家可以共同探討2008-11-11微信公眾平臺(tái)開(kāi)發(fā)之認(rèn)證"成為開(kāi)發(fā)者".Net代碼解析
這篇文章主要為大家詳細(xì)解析了微信公眾平臺(tái)開(kāi)發(fā)之認(rèn)證"成為開(kāi)發(fā)者".Net代碼,感興趣的小伙伴們可以參考一下2016-06-06