WPF版的HideCaret()
周银辉
事情是这样的:
一般说来,对于那些拥有句柄的TextBox(RichTextBox同理)控件,比如win32的,WinForm,如果我们想手动隐藏或显示其插入符(Caret),可以调用HideCaret和ShowCaret这样的Windows API,比如WinForm而言,我们可以这样:
public static extern bool HideCaret(IntPtr hWnd);
[DllImport( " user32.dll " )]
public static extern bool ShowCaret(IntPtr hWnd);
那个hWnd嘛,传入TextBox的句柄就可以了。
但到了WPF这里,恩,不好使了,因为在WPF中,窗口级别的东东有句柄,文本框之类的控件根本就没有。
另外,把WPF的TextBox 的 IsReadOnly属性设置为True,插入符自然没有了, 如果你的应用里面的确可以将其设置为只读的话,这是可行的,当然,我比较背,我发现将其设置成只读后在某种情况之下其光标还在那里闪啊闪,难道是WPF的BUG?反正这足够让我郁闷的了。
WPF TextBox的插入符是如何实现的:
据我的粗略”研究“表明,其根本就不是调用Win32 API来显示插入符的,其用的是一个Adorner,然后对这个Adorner做的一点动画效果。
解决方案:
那么找出这个显示的插入符的Adorner,那么隐藏起来不就OK了。但是,WPF TextBox自然不会暴露出这样的”内部组件“,所以不那么容易找啊。没关系,Reflector这样的工具能够反编译出.net api的一切东东,那么就说明要把那个Adorner找出来不是没有可能的。所以我折腾出了下面的代码:
{
var textContainer = textBox.GetPrivateProperty( " TextContainer " ).GetValue(textBox, null );
var textSelection = textContainer.GetPrivateProperty( " TextSelection " ).GetValue(textContainer, null );
var caretElement = textSelection.GetPrivateProperty( " CaretElement " ).GetValue(textSelection, null );
var caret = caretElement as Adorner;
return caret;
}
然后 caret.Visibility = Visibility.Collapsed (或Visible)便可以控制插入符的隐藏或显示了
但,郁闷的事情接踵而至,我发现,当你隐藏掉你查找出了的Adorner后,TextBox会在某些情况之下,完全重新创建一个Adorner来显示,Oh,My lady GaGa,
既然你不停地创建,那么我就不停地扼杀吧,呵呵呵,完整的代码如下:
{
private static Thread GetBackgourndThread(DependencyObject obj)
{
return (Thread)obj.GetValue(BackgourndThreadProperty);
}
private static void SetBackgourndThread(DependencyObject obj, Thread value)
{
obj.SetValue(BackgourndThreadProperty, value);
}
private static readonly DependencyProperty BackgourndThreadProperty =
DependencyProperty.RegisterAttached( " BackgourndThread " , typeof (Thread), typeof (CaretHelper), new UIPropertyMetadata( null ));
public static void HideCaret( this TextBoxBase textBox)
{
var pts = new ParameterizedThreadStart(HideCaretCore);
var thread = GetBackgourndThread(textBox);
if (thread == null )
{
thread = new Thread(pts) {IsBackground = true };
SetBackgourndThread(textBox, thread);
thread.Start(textBox);
}
else
{
try
{
#pragma warning disable 618,612
thread.Resume();
#pragma warning restore 618,612
}
// ReSharper disable EmptyGeneralCatchClause
catch
// ReSharper restore EmptyGeneralCatchClause
{
}
}
}
private static void HideCaretCore( this object textBox)
{
while ( true )
{
var caret = ((TextBoxBase)textBox).GetCaret();
if (caret != null )
{
Action a = () => caret.Visibility = Visibility.Collapsed;
caret.Dispatcher.Invoke(a, null );
}
Thread.Sleep( 100 );
}
// ReSharper disable FunctionNeverReturns
}
// ReSharper restore FunctionNeverReturns
public static void ShowCaret( this TextBoxBase textBox)
{
var thread = GetBackgourndThread(textBox);
if (thread != null )
{
#pragma warning disable 618,612
thread.Suspend();
#pragma warning restore 618,612
}
var caret = textBox.GetCaret();
if (caret != null )
{
caret.Visibility = Visibility.Visible;
}
}
private static Adorner GetCaret( this TextBoxBase textBox)
{
var textContainer = textBox.GetPrivateProperty( " TextContainer " ).GetValue(textBox, null );
var textSelection = textContainer.GetPrivateProperty( " TextSelection " ).GetValue(textContainer, null );
var caretElement = textSelection.GetPrivateProperty( " CaretElement " ).GetValue(textSelection, null );
var caret = caretElement as Adorner;
return caret;
}
private static PropertyInfo GetPrivateProperty( this object obj, string name)
{
return obj.GetType().GetProperty(name, BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Instance);
}
}