捕获用户在类似于 WPF 文本框的自定义控件中的单击和键入
本文关键字:自定义控件 单击 用户 类似于 文本 WPF | 更新日期: 2023-09-27 18:13:42
尝试在 C# 和 WPF 中制作一个像 Microsoft Word 中的公式编辑器。 不能使用XML;它必须是纯粹的程序化的。
现在我有LineGUIObject : System.Windows.Controls.WrapPanel
,就像System.Windows.Controls.TextBox
一样,除了它不只是显示字符串,而是按顺序显示List<System.Windows.UIElement>
的每个元素。
现在,我希望用户能够单击LineGUIObject
的实例并键入它。 问题是我不知道如何捕获用户的点击或阅读他们键入的输入。 如何做到这一点?
注意:这个问题不是问如何处理捕获后的输入;只是如何首先获取输入。 例如,是否有一些event
在用户单击它或其他东西后触发? 我似乎找不到一个System.Windows.Controls.WrapPanel
,这可能意味着我需要使用其他类型的对象,或者..?
当前代码:
public class LineGUIObject
: System.Windows.Controls.WrapPanel
{
private List<System.Windows.UIElement> _uiElementList;
private CursorGUIObject _cursor;
private int? _cursorIndex;
public LineGUIObject(System.Windows.Threading.Dispatcher dispatcher)
: base()
{
this.UIElementList = new List<System.Windows.UIElement>();
this.Cursor = new CursorGUIObject(dispatcher, 25, 1.5, 250);
this.UIElementList.Add(this.Cursor);
this.AddText("[junk string just to see this otherwise invisible object while debugging]");
}
protected void InterpretUserKeyStroke(/* ??? */)
{
//How do we get this method to be called on user input,
//e.g. when the user types "1"?
throw new NotImplementedException();
}
protected void AddText(string text)
{
this.UIElementList.Add(new System.Windows.Controls.TextBlock(new System.Windows.Documents.Run(text)));
this.UpdateDisplay();
}
protected List<System.Windows.UIElement> UIElementList { get { return this._uiElementList; } private set { this._uiElementList = value; } }
protected CursorGUIObject Cursor { get { return this._cursor; } private set { this._cursor = value; } }
protected int? CursorIndex
{
get { return this._cursorIndex; }
set
{
int? nullablePriorIndex = this.CursorIndex;
if (nullablePriorIndex != null)
{
int priorIndex = nullablePriorIndex.Value;
this.UIElementList.RemoveAt(priorIndex);
}
if (value == null)
{
this._cursorIndex = null;
}
else
{
int newIndex = value.Value;
if (newIndex < 0)
{
newIndex = 0;
}
else
{
int thisListCount = this.UIElementList.Count;
if (newIndex > thisListCount) { newIndex = thisListCount; }
}
this.UIElementList.Insert(newIndex, this.Cursor);
this._cursorIndex = newIndex;
}
this.UpdateDisplay();
}
}
protected void UpdateDisplay()
{
this.Children.Clear();
foreach (System.Windows.UIElement uiElement in this.UIElementList) { this.Children.Add(uiElement); }
}
}
public class CursorGUIObject
: System.Windows.Controls.WrapPanel
{
public const double MINIMUM_BLINK_TIME_IN_MS = 5;
public const double MINIMUM_HEIGHT = 0.5;
public const double MINIMUM_WIDTH = 0.5;
private object ToggleVisibilityLock = new object();
private delegate void TimerIntervalDelegate();
private System.Windows.Shapes.Rectangle _rectangle;
private System.Timers.Timer _timer;
private System.Windows.Threading.Dispatcher _dispatcher;
public CursorGUIObject(System.Windows.Threading.Dispatcher dispatcher, double height, double width, double blinkTimeInMS)
{
this.Dispatcher = dispatcher;
System.Windows.Shapes.Rectangle rectangle = new System.Windows.Shapes.Rectangle();
rectangle.Width = width > MINIMUM_WIDTH ? width : MINIMUM_WIDTH;
rectangle.Height = height > MINIMUM_HEIGHT ? height : MINIMUM_HEIGHT;
rectangle.Fill = System.Windows.Media.Brushes.Black;
this.Rectangle = rectangle;
this.Children.Add(rectangle);
System.Timers.Timer timer = new System.Timers.Timer(blinkTimeInMS > MINIMUM_BLINK_TIME_IN_MS ? blinkTimeInMS : MINIMUM_BLINK_TIME_IN_MS);
this.Timer = timer;
timer.Elapsed += timer_Elapsed;
timer.Start();
}
~CursorGUIObject()
{
System.Timers.Timer timer = this.Timer;
if (timer != null) { timer.Dispose(); }
}
private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
Delegate timerDelegate = new TimerIntervalDelegate(ToggleVisibility);
this.Dispatcher.BeginInvoke(timerDelegate);
}
protected void ToggleVisibility()
{
lock (ToggleVisibilityLock)
{
if (this.Rectangle.Visibility.Equals(System.Windows.Visibility.Hidden))
{
this.Rectangle.Visibility = System.Windows.Visibility.Visible;
}
else
{
this.Rectangle.Visibility = System.Windows.Visibility.Hidden;
}
}
}
protected System.Windows.Shapes.Rectangle Rectangle { get { return this._rectangle; } private set { this._rectangle = value; } }
protected System.Timers.Timer Timer { get { return this._timer; } private set { this._timer = value; } }
protected System.Windows.Threading.Dispatcher Dispatcher { get { return this._dispatcher; } private set { this._dispatcher = value; } }
}
几乎所有 WPF 控件都提供对 UIElement.PreviewMouseDown
事件的访问,您可以使用该事件来监视鼠标单击。因此,此事件允许您监视单击每个对象的时间。接下来,我建议您使用一个小的Popup
控件来弹出一个TextBox
,用户可以使用该输入值:
<Popup Name="Popup">
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="5" Padding="5">
<TextBox Text="{Binding InputText}" />
</Border>
</Popup>
根据设置项目的方式,可以从事件处理程序打开Popup
:
private void YourObject_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
Popup.IsOpen = true;
}
事实证明,LineGUIObject
只需要在其构造函数中设置this.Focusable = true;
,以便在单击时可以接收键盘的焦点。
现在它可以专注于,this.KeyUp += LineGUIObject_KeyUp;
也可以在构造函数中,并且
protected override void OnKeyDown(System.Windows.Input.KeyEventArgs e)
{
this.AddText(e.Key.ToString());
}
即使这一开始也存在问题,因为我的LineGUIObject
嵌套在一个ScrollViewer
中,该在LineGUIObject
收到它后立即窃取焦点。 这是通过使ScrollViewer
无法获得焦点来解决的,即 <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Focusable="False"/>
.