В моем приложении WPF у меня есть привязка данных TextBox
и привязка данных ItemsControl
. Содержимое ItemsControl
определяется содержимым TextBox
. Я хочу иметь возможность ввести значение в TextBox
, нажать вкладку и ввести первый элемент в ItemsControl
(созданный из значения в TextBox
). Проблема, с которой я сталкиваюсь, заключается в том, что действие вкладки оценивается до того, как WPF создаст шаблонные элементы в ItemsControl
. Следующий код демонстрирует эту проблему:
<Window x:Class="BindingExample.Window1" x:Name="SelfControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:BindingExample" Title="Window1" Height="300" Width="400">
<Window.Resources>
<DataTemplate DataType="{x:Type loc:ChildClass}">
<TextBox Text="{Binding Value}" />
</DataTemplate>
</Window.Resources>
<StackPanel DataContext="{Binding ElementName=SelfControl}" Focusable="False">
<Label Content="Options: A, B, C" />
<TextBox Text="{Binding Object.Value}" />
<ItemsControl Margin="16,0,0,0" ItemsSource="{Binding Object.Children}" Focusable="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<TextBox Text="Box2" />
</StackPanel>
</Window>
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
namespace BindingExample
{
public partial class Window1
{
public static readonly DependencyProperty ObjectProperty = DependencyProperty.Register("Object", typeof(ParentClass), typeof(Window1));
public ParentClass Object
{
get { return GetValue(ObjectProperty) as ParentClass; }
set { SetValue(ObjectProperty, value); }
}
public Window1()
{
InitializeComponent();
Object = new ParentClass();
}
}
public class ParentClass : INotifyPropertyChanged
{
private string value;
public string Value
{
get { return value; }
set { this.value = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Children")); }
}
public IEnumerable<ChildClass> Children
{
get
{
switch (Value.ToUpper())
{
case "A": return new ChildClass[] { "A-1", "A-2", "A-2" };
case "B": return new ChildClass[] { "B-1", "B-2", "B-3" };
case "C": return new ChildClass[] { "C-1", "C-2", "C-2" };
default: return new ChildClass[] { "Unknown" };
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class ChildClass
{
public string Value { get; set; }
public static implicit operator ChildClass(string value) { return new ChildClass { Value = value }; }
}
}
В этом коде я хотел бы ввести «A» в первый TextBox
, нажать клавишу Tab и сместить фокус на дочерний элемент TextBox
с текстом «A-1». Вместо этого фокус переходит на TextBox с текстом «Box2». Как я могу добиться поведения, которое я ищу здесь?
Примечание. Как указал Жюльен Пулен, это можно сделать, переключив UpdateSourceTrigger
на TextBox
на PropertyChanged
. Однако это работает только в том случае, если допустима привязка «по мере ввода». В моем случае я также хотел бы установить значение и вкладку одним нажатием клавиши. Есть ли способ заставить ItemsControl создавать свои шаблонные элементы по требованию?
2 ответа
Попробуйте установить UpdateMode
TextBox
в PropertyChanged
, чтобы базовое значение устанавливалось при вводе нового значения, а не тогда, когда TextBox
теряет фокус:
<TextBox Text="{Binding Path=Object.Value, UpdateMode=PropertyChanged}" />
Вот альтернативное решение. Это немного хак, но, похоже, работает. Поскольку шаблонные объекты в ItemsControl
не создаются до тех пор, пока выполнение в основном потоке не приостановится, этот код перехватывает вкладку, обновляет привязку и устанавливает таймер для перемещения фокуса, как только элементы будут созданы. .
...
<TextBox Text="{Binding Object.Value}" KeyDown="TextBox_KeyDown" />
...
public partial class Window1
{
private DispatcherTimer timer;
...
private void TextBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Tab && e.KeyboardDevice.Modifiers != ModifierKeys.Shift)
{
e.Handled = true;
var textbox = (TextBox)sender;
textbox.GetBindingExpression(TextBox.TextProperty).UpdateSource();
(timer = new DispatcherTimer(
new TimeSpan(100000), // 10 ms
DispatcherPriority.Normal,
delegate
{
textbox.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
timer.Stop();
}, Dispatcher)).Start();
}
}
}
Единственная проблема, которую я вижу с этим кодом, заключается в том, что для заполнения ItemsControl
может потребоваться больше 10 мс, и в этом случае Tab перепрыгнет через эти элементы. Есть ли способ определить, произошло ли создание элементов ItemsControl
?
Похожие вопросы
Новые вопросы
c#
C# (произносится как «see Sharp») — это высокоуровневый мультипарадигменный язык программирования со статической типизацией, разработанный Microsoft. Код C# обычно нацелен на семейство инструментов и сред выполнения Microsoft .NET, которое включает в себя .NET, .NET Framework, .NET MAUI и Xamarin среди прочих. Используйте этот тег для ответов на вопросы о коде, написанном на C#, или о формальной спецификации C#.