Вкладка WPF в элементы управления данными - PullRequest
1 голос
/ 21 июля 2009

В моем приложении 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, нажать клавишу табуляции и переместить фокус на дочерний элемент TextBox с текстом «A-1». Вместо этого фокус переходит к TextBox с текстом «Box2». Как я могу добиться того поведения, которое ищу здесь?

Примечание: Как указал Жюльен Пулин, эту работу можно выполнить, переключив UpdateSourceTrigger на TextBox на PropertyChanged. Это работает, однако, только если привязка «по мере ввода» приемлема. В моем случае я также хотел бы установить значение и вкладку одним нажатием клавиши. Есть ли способ заставить ItemsControl создавать свои шаблонные элементы по требованию?

Ответы [ 2 ]

1 голос
/ 21 июля 2009

Попробуйте установить UpdateMode для TextBox на PropertyChanged, чтобы базовое значение устанавливалось при вводе нового значения вместо того, когда TextBox теряет фокус:

<TextBox Text="{Binding Path=Object.Value, UpdateMode=PropertyChanged}" />
0 голосов
/ 23 июля 2009

Вот альтернативное решение. Это немного взломать, но, похоже, работает. Поскольку шаблонные объекты в 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 элементов?

...