0

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.

Community
  • 1
  • 1
Bob Machine
  • 141
  • 4
  • 12
  • How do you set the datacontext of your listbox? If you have a list of objects, you can cast your ListBoxItem to the type of the object in your list and inside your object you can have a property bound to the visibility of your control and toggle visibility. Can you give some code? – Mihai Hantea Mar 03 '14 at 01:49
  • 2
    **Don't manipulate UI elements in procedural code in WPF. That's what XAML is for.** - Use proper DataBinding instead. Whatever UI element you need to change properties for, do so using proper DataBinding instead of using `VisualTreeHelper` and stuff like that which is too complicated and might not even work due to UI virtualization and other complex Visual-Tree related concepts. – Federico Berasategui Mar 03 '14 at 01:51
  • @MihaiHantea could you show me an example of what you mean? I have a viewModel and i set the ListBox item source to it, is that what your referring to? – Bob Machine Mar 03 '14 at 01:53
  • @HighCore I understand what you mean, but in this instance this seems to be the most efficient approach, the items displayed are from a a database and doing it the way your saying would require constant reloads. – Bob Machine Mar 03 '14 at 02:03
  • 1
    @BobMachine wrong, a winforms-like approach is never "the most efficient" in WPF. WPF is NOT winforms, and no it's not the most efficient because manipulating `ItemsControl`s in procedural code may break UI virtualization inadvertently, causing a HUGE performance loss. – Federico Berasategui Mar 03 '14 at 02:43
  • @BobMachine BTW, WPF DataBinding has nothing to do with databases, so no, it doesn't require "constant reloading" whatever that means. – Federico Berasategui Mar 03 '14 at 02:51

2 Answers2

2

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

}
Mihai Hantea
  • 1,741
  • 13
  • 16
  • thanks for your example, but that's not what I'm trying to do. what I'm trying to do is find the element by name at a specific index or to find element by tag, that would work too. – Bob Machine Mar 03 '14 at 03:26
  • 1
    Mihai is suggesting to NOT do that, but instead change a property on a ViewModel that will collapse (hide) the image. You can get the viewmodel very easily from the ListBox provided the ListBox is bound to a collection of that viewmodel. SelectedItem will be the viewmodel is question, or the Items property will give you the viewmodel from the index. It is much easier to use this pattern – Shawn Kendrot Mar 03 '14 at 06:23
  • @ShawnKendrot but the thing is, if user scrolls the ListBox (up/down) there is no SelectedItem only if user clicks on item. On Win8 there is TopIndex and one can use that to determine item on view and process accordingly, but that's not available on WP8, so I'm using VerticalOffset to calculate the index on view and now i need to process that. So the only options i see is to find by name at the specified index or find by it's tag property. And i hane not figured out a way to do that. PS. Sorry there was a typo on my previous comment – Bob Machine Mar 03 '14 at 11:12
  • That's fine. Use whatever you want to get the index, then use the Items property of the ListBox to get the item `var item = listBox.Items[index];` then change your property. What is it you are trying to accomplish by changing the visibility of the image? – Shawn Kendrot Mar 03 '14 at 14:45
  • Give us some code to see what are you trying to do. – Mihai Hantea Mar 03 '14 at 14:47
  • @MihaiHantea Tanks for the help, i found a solution and posted it. – Bob Machine Mar 03 '14 at 19:10
  • @LordTakkera Thanks, I only wished someone had pointed it out to me instead of banging my head against the wall for 2 days.. – Bob Machine Mar 03 '14 at 23:23
0

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...

Bob Machine
  • 141
  • 4
  • 12
  • Yes you can use Tag in this purpose to store your custom information about the image. But what we were trying to say is going through your controls every time is not a very good approach. Anyway, great that you solve it by yourself. – Mihai Hantea Mar 04 '14 at 01:09
  • @MihaiHantea Well i couldn't find any other to do it, but what i also added later was a list to keep track of the changed ones and not have to go through the controls every time, but thanks for your effort to help :) – Bob Machine Mar 04 '14 at 12:36