How to access element by name at a specific ListBo

2019-09-07 03:40发布

问题:

I have a ListBox with several items on it (TextBlocks, Images and so on), what I'm trying to do is access an element by it's name at a specific ListBox index.

I know the element name and the index i need to access, in this case I need to change the visibility property of an image control to collapsed.

I've looked at a few examples using VisualTreeHelper here but they were only to access element by name, not by name and index, which is what i need to do but have not been able to.

Thanks, Bob.

回答1:

I implemented a small demo to emphasize data binding using MVVM patern. In this example I toggle the TextBlock visibility using ShowTextbox property bound to the TextBlock.Visibility by un/checking the Checkbox.

App.xaml.cs

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        var mainViewModel = new MainViewModel
        {
            ListItems = new ObservableCollection<MyModel>
            {
                new MyModel
                {
                    MyPropertyText = "hello",
                    ShowText = true,
                    ShowTextbox = Visibility.Visible
                }
            }
        };
        var app = new MainWindow() {DataContext = mainViewModel};
        app.Show();
    }
}

MainWindow.xaml

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:wpfApplication1="clr-namespace:WpfApplication1"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <ListBox HorizontalAlignment="Left" Height="148" VerticalAlignment="Top" Width="299" Margin="30,57,0,0" ItemsSource="{Binding Path=ListItems}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Vertical">
                    <TextBlock Text="{Binding Path=MyPropertyText}" Visibility="{Binding Path=ShowTextbox}"/>
                    <CheckBox IsChecked="{Binding ShowText}"/>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Grid>

MainWindow.cs

public partial class MainWindow : Window
{

    private MainViewModel _mainViewModel;
    public MainWindow()
    {
        InitializeComponent();           

    }
}

MainViewModel.cs

public class MainViewModel : ObservableObject
{

    public ObservableCollection<MyModel> ListItems 
    { 
        get { return _listItems; }
        set
        {
            _listItems = value;
            RaisePropertyChanged("ListItems");
        }
    } 


}

MyModel.cs

public class MyModel : ObservableObject
{
    private string _myPropertyText;
    private bool _showText;
    private Visibility _showTextbox;

    public string MyPropertyText
    {
        get { return _myPropertyText; }
        set
        {
            _myPropertyText = value;
            RaisePropertyChanged("MyPropertyText");
        }
    }

    public bool ShowText
    {
        get { return _showText; }
        set
        {
            _showText = value;
            RaisePropertyChanged("ShowText");
            ShowTextbox = value ? Visibility.Visible : Visibility.Collapsed;
        }
    }

    public Visibility ShowTextbox
    {
        get { return _showTextbox; }
        set
        {
            _showTextbox = value;
            RaisePropertyChanged("ShowTextbox");
        }
    }
}

ObservableObject.cs

public class ObservableObject : INotifyPropertyChanged
{
    #region Constructor

    public ObservableObject() { }

    #endregion // Constructor

    #region RaisePropertyChanged

    /// <summary>
    /// Raises this object's PropertyChanged event.
    /// </summary>
    /// <param name="propertyName">The property that has a new value.</param>
    protected virtual void RaisePropertyChanged(string propertyName = "")
    {
        VerifyPropertyName(propertyName);

        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            var e = new PropertyChangedEventArgs(propertyName);
            handler(this, e);
        }
    }

    #endregion

    #region Debugging Aides

    /// <summary>
    /// Warns the developer if this object does not have
    /// a public property with the specified name. This 
    /// method does not exist in a Release build.
    /// </summary>
    [Conditional("DEBUG")]
    [DebuggerStepThrough]
    public void VerifyPropertyName(string propertyName)
    {
        // If you raise PropertyChanged and do not specify a property name,
        // all properties on the object are considered to be changed by the binding system.
        if (String.IsNullOrEmpty(propertyName))
            return;

        // Verify that the property name matches a real,  
        // public, instance property on this object.
        if (TypeDescriptor.GetProperties(this)[propertyName] == null)
        {
            string msg = "Invalid property name: " + propertyName;

            if (this.ThrowOnInvalidPropertyName)
                throw new ArgumentException(msg);
            else
                Debug.Fail(msg);
        }
    }

    /// <summary>
    /// Returns whether an exception is thrown, or if a Debug.Fail() is used
    /// when an invalid property name is passed to the VerifyPropertyName method.
    /// The default value is false, but subclasses used by unit tests might 
    /// override this property's getter to return true.
    /// </summary>
    protected virtual bool ThrowOnInvalidPropertyName { get; private set; }

    #endregion // Debugging Aides

    #region INotifyPropertyChanged Members

    /// <summary>
    /// Raised when a property on this object has a new value.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    #endregion // INotifyPropertyChanged Members

}


回答2:

I ended up going about it in a slightly different way, based on the example found here and using the elements' tag property instead, just changing the element from a TextBlock to an Image

This way i could bind the element Tag, and use that later on do target the needed items.

private void SearchVisualTree(DependencyObject targetElement, string _imageTag)
{
  var count = VisualTreeHelper.GetChildrenCount(targetElement);
  if (count == 0)
  return;

for (int i = 0; i < count; i++)
{
    var child = VisualTreeHelper.GetChild(targetElement, i);
    if (child is Image)
    {
        Image targetItem = (Image)child;

        if (targetItem.Tag as string == _imageTag && targetItem.Visibility == Visibility.Visible)
        {
            targetItem.Visibility = Visibility.Collapsed;
            return;
        }
    }
    else
    {
        SearchVisualTree(child, _imageTag);
    }
  }
}

Hopefully this helps someone else in the future...