简体   繁体   English

如何从作为listBoxItem的堆栈面板访问标签

[英]How can I access a label from a stackpanel that is a listBoxItem

For a list box in WPF I have this template: 对于WPF中的列表框,我有以下模板:

<ControlTemplate x:Key="ListBoxItemControlTemplate1" TargetType="{x:Type ListBoxItem}">
    <StackPanel Orientation="Horizontal" Background="Silver">
        <CheckBox Content="CheckBox" VerticalAlignment="Center"/>
        <Label Content="Label" Padding="5,0" Width="260" VerticalAlignment="Center" Background="#F3D6D6D6" Margin="5,0"/>
        <Button Content="Edit" Width="Auto" Padding="1" Margin="2.5,0" HorizontalAlignment="Right" Click="Button_Click"/>
    </StackPanel>
</ControlTemplate>

And whenever I press the corresponding button for the listBoxItem I want to modify the label of the same listBoxItem, preferably without using a name if possible. 每当我按下listBoxItem的相应按钮时,我都想修改同一listBoxItem的标签,最好尽可能不使用名称。
I was thinking maybe there is a way of saying "use the Label from the parent of this button" which I thought would be the StackPanel, but can't find anything useful on the internet. 我当时在想也许有一种方式可以说“使用此按钮的父级标签”,我以为是StackPanel,但在互联网上找不到任何有用的东西。

I think the better solution is to use a view model with a DataTemplate, once you have the code set up you can re-use it over and over with very little chance of error. 我认为更好的解决方案是将视图模型与DataTemplate一起使用,一旦设置了代码,就可以一次又一次地重复使用它,而出错的机会很小。

Here is what your view model will look like 这是您的视图模型的外观

public class ViewModel : INotifyPropertyChanged
{
    private ObservableCollection<ItemViewModel> _items;

    public ViewModel()
    {
        _items = new ObservableCollection<ItemViewModel>(new List<ItemViewModel>()
            {
                new ItemViewModel() { Label = "Item1", IsChecked = false },
                new ItemViewModel() { Label = "Item2", IsChecked = true },
                new ItemViewModel() { Label = "Item3", IsChecked = true },
                new ItemViewModel() { Label = "Item4", IsChecked = false },
                new ItemViewModel() { Label = "Item5", IsChecked = false },
            });

    }

    public ObservableCollection<ItemViewModel> Items
    {
        get
        {

            return this._items;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    // Create the OnPropertyChanged method to raise the event
    protected void OnPropertyChanged(string name)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }
}

public class ItemViewModel  : INotifyPropertyChanged
{
    private bool _isChecked = false;
    private string _label = "Label";

    public ICommand ButtonCommand { get; private set; }

    public ItemViewModel()
    {
        this.ButtonCommand = new DelegateCommand(Com_ButtonCommand);
    }

    public void Com_ButtonCommand(object parameter)
    {
        this.Label = "New Label text";
    }

    public string Label
    {
        get
        {
            return this._label;
        }
        set
        {
            this._label = value;
            this.OnPropertyChanged("Label");
        }
    }

    public bool IsChecked
    {
        get
        {
            return this._isChecked;
        }
        set
        {
            this._isChecked = value;
            this.OnPropertyChanged("IsChecked");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    // Create the OnPropertyChanged method to raise the event
    protected void OnPropertyChanged(string name)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }
}
public class DelegateCommand : ICommand
{
    private readonly Predicate<object> _canExecute;
    private readonly Action<object> _execute;

    public event EventHandler CanExecuteChanged;

    public DelegateCommand(Action<object> execute)
        : this(execute, null)
    {
    }

    public DelegateCommand(Action<object> execute,
                   Predicate<object> canExecute)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        if (_canExecute == null)
        {
            return true;
        }

        return _canExecute(parameter);
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    public void RaiseCanExecuteChanged()
    {
        if (CanExecuteChanged != null)
        {
            CanExecuteChanged(this, EventArgs.Empty);
        }
    }
}

There are 3 classes here, 1 of them a helper. 这里有3个课程,其中1个是帮助者。

ViewModel --> Your main ViewModel, ItemViewModel --> Model for each item, DelegateCommand --> Allows you to map the button to the view model ViewModel->您的主ViewModel,ItemViewModel->每个项目的模型,DelegateCommand->允许您将按钮映射到视图模型

your xaml will look like this 您的xaml将如下所示

<ListBox ItemsSource="{Binding Items}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal" Background="Silver">
                        <CheckBox IsChecked="{Binding IsChecked}" Content="CheckBox" VerticalAlignment="Center"/>
                        <Label Content="{Binding Label}" Padding="5,0" Width="260" VerticalAlignment="Center" Background="#F3D6D6D6" Margin="5,0"/>
                        <Button Command="{Binding ButtonCommand}" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

Notice the "{Binding}" keyword, this "binds" each datatemplate to a member with that name on its own view model, in this case IsChecked and Label. 注意关键字“ {Binding}”,此关键字将每个数据模板“绑定”到在其自己的视图模型上具有该名称的成员,在本例中为IsChecked和Label。

To load your ViewModel add the following line to the code-behind in your usercontrol(using MVVM you will rarely touch the code-behind of usercontrols at all). 要加载ViewModel,请在用户控件的代码后面添加以下行(使用MVVM,您很少会碰到用户控件的代码后面)。

this.DataContext = new ViewModel();

When its your first time seeing a viewmodel it may seem like a lot of work but its mostly re-usable and is the defacto standard for doing things like this (MVVM), Ive included all necessary code to get you started. 当您第一次看到视图模型时,它看起来可能需要完成很多工作,但是它几乎可以重用,并且是执行此类操作(MVVM)的事实上的标准,Ive包含了所有必要的代码来帮助您入门。

The following class as well as DelegateCommand should be kept for later use, I have already included it in the above snippet 下面的类以及DelegateCommand应该保留供以后使用,我已经在上面的代码段中包含了它。

    public class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        // Create the OnPropertyChanged method to raise the event
        protected void OnPropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }
    }

I'd recommend you to use a viewmodel in the back. 我建议您在后面使用视图模型。 That VM exposes a command that is bound to the button. 该VM公开了绑定到该按钮的命令。 In addition it exposes a DependencyProperty containing the name for the label. 此外,它还公开了一个DependencyProperty,其中包含标签的名称。 And the label is bound to that property. 并且标签绑定到该属性。 Now if you press the button, the command is executed, which changes the label text and via databinding the new text will be updated on the label. 现在,如果您按下按钮,将执行命令,该命令将更改标签文本,并且通过数据绑定,新文本将在标签上更新。

The other option, I wouldn't recommend, is to use FindName to find the Label. 我不建议使用的另一种方法是使用FindName查找标签。 Or really bad (but works) is to iterate over the controls using the VisualTreeHelper. 或真的很糟糕(但可行)是使用VisualTreeHelper遍历控件。

I would navigate the VisualTree to find the parent StackPanel , then search that StackPanel to find a child Label to update. 我将在VisualTree中导航以找到父StackPanel ,然后搜索该StackPanel以找到要更新的子Label

If you're interested, I have some VisualTreeHelpers posted on my blog that would make this easy: 如果您有兴趣,我会在我的博客上发布一些VisualTreeHelpers ,这将使此操作变得容易:

var parent = VisualTreeHelpers.FindAncestor<StackPanel>((Button)sender);
if (parent == null) return;

var lbl = VisualTreeHelpers.FindChild<Label>(parent);
if (lbl == null) return;

lbl.Content = "Some Text";

This is providing I'm not using the MVVM design pattern. 前提是我没有使用MVVM设计模式。 If I were using MVVM, I would be storing the Label.Content property in the ViewModel , and the Button command should point to a Command in the ViewModel , and it should pass it the DataBound item as the CommandParameter so it knows which Label to update. 如果使用的是MVVM,则将Label.Content属性存储在ViewModel ,并且Button命令应指向ViewModelCommand ,并且应该将DataBound项作为CommandParameter传递给它,以便它知道要更新哪个Label 。

<ControlTemplate x:Key="ListBoxItemControlTemplate1" TargetType="{x:Type ListBoxItem}">
    <StackPanel Orientation="Horizontal" Background="Silver">
        <CheckBox Content="CheckBox" VerticalAlignment="Center"/>
        <Label Content="{Binding SomeText}" ... />
        <Button Content="Edit" 
                Command="{Binding ElementName=MyListBox, Path=DataContext.EditCommand}"
                CommandParameter="{Binding }" />
    </StackPanel>
</ControlTemplate>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM