WPF 如何在 DataTemplate 中切换 UI 元素的可见性

3

我有一个如下所示的DataTemplate定义:

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

我想实现在用户单击 StackPanel 时可以切换 TextBlock 和 TextBox 的可见性。我尝试在 StackPanel 上设置 MouseLeftButtonUp 事件处理程序,但这会抛出一个异常“Object reference not set to an instance of an object”。是否有其他方法可以实现此功能?也许可以在 XAML 中使用触发器来实现?另外,这可能是相关的。上述模板是由模板选择器应用于 ListBox 中的两个模板之一。ListBox 本身位于 Grid 中,两个模板都在 Grid.Resources 部分中定义。
编辑1: 我尝试以下方式设置事件:
<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;
}

您想要在所选项目中显示 TextBox 而不是 TextBlock 吗? - Fredrik Hedblad
@Meleak 我想要在用户每次点击StackPanel时切换TextBlockTextBox的可见性。 - Praetorian
如果我理解正确的话,那么StackPanelListBoxItemItemTemplate中,所以TextBlock将决定StackPanel的大小。这意味着你永远无法直接点击StackPanel,因为TextBlock会覆盖它,除非你在ItemContainerStyle中设置HorizontalContentAlignmentStretch - Fredrik Hedblad
如果TextBox可见并且您单击它,您希望发生什么?是否应该切换可见性? - Fredrik Hedblad
@Meleak 是的,TextBlock 完全覆盖了 StackPanel,但是点击事件不应该冒泡到 StackPanel 吗?如果您查看上面的 XAML,最初 TextBlock 是可见的,TextBox 不可见。第一次点击 -> TextBlock 折叠,TextBox 可见。第二次点击 -> TextBlock 可见,TextBox 折叠。以此类推... - Praetorian
2个回答

1

其中一种解决方案是将 TextBoxTextBlock 的可见性绑定到作为 StackPanelDataContext 使用的类的属性上。以下是一个示例实现:

Xaml 代码:

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

还有C#代码:

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));
    }
}

0

为什么不在您的视图模型中绑定项目的可见性?

一个例子。

<Textblock Test="{Binding passwordText,ElementName=This}" Visibility="{Binding passwordTextVisibility}"/>

在你的 ViewModel 中写上:

public Visibility passwordTextVisibility
{
 getters and setters here
}

在鼠标事件中,您需要在堆栈面板内部使用某种路由事件。一个示例:

在堆栈面板内部,您需要处理鼠标事件,无论需要什么。请详细了解一下路由事件。

例如,如果 PreviewMouseLeftButtonUp 无法正常工作。

<StackPanel Mouse.MouseUp="MouseButtonUpEventHandler"/>

在视图模型中

public void MouseButtonUpEventHandler (RoutedEvent e)
{
//logic here to check if it's left mouse if it is then set visibility
}

}


我该如何将这个与鼠标点击事件绑定? - Praetorian
为PreviewMouseLeftButtonUp设置事件处理程序会抛出相同的异常(Mouse.PreviewMouseUp也是如此)。无论如何,如果我需要设置事件处理程序,我只需获取StackPanel及其Children,然后在代码中切换可见性,而不是搞乱视图模型。 - Praetorian
你可以尝试在XAML中使用Mouse.MouseUp,而不是使用MouseLeftButtonUp吗? - Kevin
private void ButtonOkClicked(object sender, RoutedEventArgs e) {Mouse mouseUp= (Mouse) sender; //逻辑处理} - Kevin
同样的事情,我认为这里有另一个潜在的问题与DataTemplate有关,它阻止我设置任何事件处理程序。也许是因为它被DataTemplateSelector所选择... - Praetorian
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接