WPF Toggle visibility of UIElements in DataTemplate

Praetorian picture Praetorian · Aug 26, 2011 · Viewed 7.6k times · Source

I have a DataTemplate defined as follows

<DataTemplate x:Key="PasswordViewerTemplate">
  <StackPanel>
    <TextBlock Text="{Binding PasswordChar, ElementName=this}"
               Visibility="Visible" />
    <TextBox Text="{Binding PasswordText}"
             Visibility="Collapsed" />
  </StackPanel>
</DataTemplate>

I want to be able to toggle visibilities of the TextBlock and the TextBox each time the user clicks on the StackPanel. I tried setting a MouseLeftButtonUp event handler on the StackPanel but this throws an exception

Object reference not set to an instance of an object

Is there another way to achieve this? Maybe in XAML itself using triggers?

Also, this might be relevant. The above template is one of two that is applied to a ListBox by a template selector. The ListBox itself is within a Grid and both templates are defined within the Grid.Resources section.

EDIT 1
I tried setting the event as follows

<StackPanel MouseLeftButtonUp="OnPasswordViewerMouseLeftButtonUp">
...
</StackPanel>
private void OnPasswordViewerMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
  var sp = sender as StackPanel;
  if( ( sp == null ) || ( sp.Children.Count != 2 ) ) {
    return;
  }

  var passwordText = sp.Children[0] as TextBlock;
  var plainText = sp.Children[1] as TextBox;
  if( ( passwordText == null ) || ( plainText == null ) ) {
    return;
  }

  passwordText.Visibility = ( passwordText.Visibility == Visibility.Visible ) ? 
    Visibility.Collapsed : Visibility.Visible;
  plainText.Visibility = ( plainText.Visibility == Visibility.Visible ) ?
    Visibility.Collapsed : Visibility.Visible;
}

Answer

makagonov picture makagonov · Oct 18, 2011

One of the solutions is to bind visibility of the TextBox and TextBlock to properties of the class which is used as DataContext for the StackPanel. Here is a sample implementation:

Xaml code:

<Grid>
    <Grid.Resources>
        <DataTemplate x:Key="PasswordViewerTemplate">
            <StackPanel PreviewMouseUp="StackPanel_PreviewMouseUp">
                <TextBlock Text="{Binding Path=PasswordChar}"
           Visibility="{Binding Path=TextBlockVisibility}" />
                <TextBox Text="{Binding Path=PasswordText}"
         Visibility="{Binding Path=TextBoxVisibility}" />
            </StackPanel>
        </DataTemplate>
    </Grid.Resources>
    <ListBox x:Name="lbox" ItemTemplate="{StaticResource ResourceKey=PasswordViewerTemplate}" ItemsSource="{Binding}"/>
</Grid>

And C# code:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        ObservableCollection<Some> items = new ObservableCollection<Some>();
        for (int i = 0; i < 10; i++)
        {
            items.Add(new Some(string.Format("passwordChar {0}", i + 1), string.Format("passwordText {0}", i + 1), Visibility.Visible, Visibility.Collapsed));
        }
        this.lbox.ItemsSource = items;
    }

    private void StackPanel_PreviewMouseUp(object sender, MouseButtonEventArgs e)
    {
        Some some = (sender as StackPanel).DataContext as Some;
        some.TextBlockVisibility = ToggleVisibility(some.TextBlockVisibility);
        some.TextBoxVisibility = ToggleVisibility(some.TextBoxVisibility);
    }
    private Visibility ToggleVisibility(Visibility visibility)
    {
        return visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
    }
}
public class Some:INotifyPropertyChanged
{
    private string _passwordChar;
    private string _passwordText;
    private Visibility _textBlockVisibility, _textBoxVisibility;

    public string PasswordChar { get { return this._passwordChar; } set { this._passwordChar = value; } }
    public string PasswordText { get { return this._passwordText; } set { this._passwordText = value; } }
    public Visibility TextBlockVisibility 
    { 
        get { return this._textBlockVisibility; } 
        set 
        { 
            this._textBlockVisibility = value;
            RaisePropertyChanged("TextBlockVisibility");
        }

    }
    public Visibility TextBoxVisibility 
    { 
        get { return this._textBoxVisibility; }
        set 
        { 
            this._textBoxVisibility = value;
            RaisePropertyChanged("TextBoxVisibility");
        }
    }

    public Some(string passwordChar, string passwordText, Visibility textBlockVisibility, Visibility textBoxVisibility)
    {
        this._passwordChar = passwordChar;
        this._passwordText = passwordText;
        this._textBlockVisibility = textBlockVisibility;
        this._textBoxVisibility = textBoxVisibility;
    }


    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged(string name)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(name));
    }
}