Как определить, является ли элемент последним в шаблоне WPF ItemTemplate? - PullRequest
0 голосов
/ 20 марта 2010

У меня есть немного XAML

<ItemsControl Name="mItemsControl">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <TextBox Text="{Binding Mode=OneWay}" KeyUp="TextBox_KeyUp"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

это связано с простой коллекцией ObservableCollection

private ObservableCollection<string> mCollection = new ObservableCollection<string>();

public MainWindow()
{
    InitializeComponent();

    this.mCollection.Add("Test1");
    this.mCollection.Add("Test2");
    this.mItemsControl.ItemsSource = this.mCollection;
}

После нажатия клавиши ввода в last TextBox я хочу, чтобы появился еще один TextBox. У меня есть код, который делает это, но есть пробел:

private void TextBox_KeyUp(object sender, KeyEventArgs e)
{
    if (e.Key != Key.Enter)
    {
        return;
    }

    TextBox textbox = (TextBox)sender;

    if (IsTextBoxTheLastOneInTheTemplate(textbox))
    {
        this.mCollection.Add("A new textbox appears!");
    }
}

Функция IsTextBoxTheLastOneInTheTemplate () - это то, что мне нужно, но я не могу понять, как писать. Как мне написать?

Я рассмотрел использование ItemsControl.ItemContainerGenerator, но не могу собрать все части вместе.

Спасибо!

-Mike

Ответы [ 3 ]

0 голосов
/ 20 марта 2010

Мне удалось найти достойное решение, обратившись к http://drwpf.com/blog/2008/07/20/itemscontrol-g-is-for-generator/. Не супер-элегантно, но у меня это сработало.

    private void TextBox_KeyUp(object sender, KeyEventArgs e)
    {
        if (e.Key != Key.Enter)
        {
            return;
        }

        TextBox textbox = (TextBox)sender;

        var lastContainer = this.mItemsControl.ItemContainerGenerator.ContainerFromIndex(this.mItemsControl.Items.Count - 1);

        var visualContainer = (Visual)lastContainer;

        var containedTextbox = (TextBox)GetDescendantByType(visualContainer, typeof(TextBox));

        var isSame = textbox == containedTextbox;

        if (isSame)
        {
             this.mCollection.Add("A new textbox appears!");
        }
    }


    public static Visual GetDescendantByType(Visual element, Type type)
    {
        if (element.GetType() == type) return element;

        Visual foundElement = null;

        if (element is FrameworkElement)
            (element as FrameworkElement).ApplyTemplate();

        for (int i = 0;
            i < VisualTreeHelper.GetChildrenCount(element); i++)
        {
            Visual visual = VisualTreeHelper.GetChild(element, i) as Visual;
            foundElement = GetDescendantByType(visual, type);
            if (foundElement != null)
                break;
        }

        return foundElement;
    }
0 голосов
/ 20 марта 2010

Мне кажется, что такое поведение лучше всего определено в модели представления:

public class ItemCollection : ObservableCollection<Item>
{
    public ItemCollection()
    {
        // this guarantees that any instance created always has at least one
        // item in it - you don't need this if you're creating instances in
        // code, but if you just create them in XAML you do.
        Item item = new Item(this);
        Add(item);
    }
}

public class Item
{
    internal Item(ItemCollection owner)
    {
        Owner = owner;
    }

    public bool IsLast
    {
        get
        {
            return Owner.LastOrDefault() == this;
        }
    }

    private ItemCollection Owner { get; set; }

    private string _Value;

    // here's the actual behavior:  if the last item in the collection is
    // given a non-empty Value, a new item gets added after it.
    public string Value
    {
        get { return _Value; }
        set
        {
            _Value = value;
            if (IsLast && !String.IsNullOrEmpty(_Value))
            {
                Owner.Add(new Item(Owner));
            }
        }
    }
}

С этого момента просто TextBox обновить источник, когда пользователь нажимает ENTER:

<DataTemplate DataType="{x:Type local:Item}">
    <TextBox Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=Explicit}"
             KeyUp="TextBox_KeyUp"/>
</DataTemplate>

С обработчиком событий KeyUp:

private void TextBox_KeyUp(object sender, KeyEventArgs e)
{
    if (e.Key != Key.Enter)
    {
        return;
    }

    TextBox t = (TextBox)sender;
    BindingExpression be = t.GetBindingExpression(TextBox.TextProperty);
    be.UpdateSource();
}
0 голосов
/ 20 марта 2010

Я предполагаю, что это упрощенная версия того, над чем вы работаете. Текстовое поле с односторонней привязкой к коллекции строк не имеет смысла для меня.

Основная проблема в этом случае - использование простой строки в качестве источника элемента. Я предполагаю, что мы не можем гарантировать, что строки будут уникальными, поэтому мы не можем делать какие-либо выводы из textbox.Text. Кроме того, поскольку строки являются неизменяемыми, мы не можем использовать экземпляр строки для вывода чего-либо.

Первый шаг в решении - создать класс для хранения данных, к которым мы можем обратиться. (В данном случае это выглядит немного глупо, поскольку содержит строку.)

    class MyData
    {
        public string Value { get; set; }
    }

Ваш второй кодовый блок становится:

    ObservableCollection<MyData> mCollection = new ObservableCollection<MyData>();

    public MainWindow()
    {
        InitializeComponent();

        this.mCollection.Add(new MyData { Value = "Test1" });
        this.mCollection.Add(new MyData { Value = "Test2" });
        this.mItemsControl.ItemsSource = this.mCollection;
    }

Мы будем использовать свойство Tag текстового поля для хранения ссылки на наш источник привязки. Мы будем использовать это, чтобы обойти проблемы уникальности. XAML становится:

    <ItemsControl Name="mItemsControl">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <TextBox Text="{Binding Value}" Tag="{Binding}" KeyUp="TextBox_KeyUp"/>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

Наконец, обработчик становится:

    private void TextBox_KeyUp(object sender, KeyEventArgs e)
    {
        if (e.Key != Key.Enter)
        {
            return;
        }

        TextBox textbox = (TextBox)sender;

        if (mItemsControl.Items.IndexOf(textbox.Tag) == mItemsControl.Items.Count - 1)
        {
            this.mCollection.Add(new MyData() { Value = "A new textbox appears!" });
        }
    }
...