【问题标题】:Inline editing TextBlock in a ListBox with Data Template (WPF)使用数据模板 (WPF) 在 ListBox 中内联编辑 TextBlock
【发布时间】:2018-11-08 17:26:47
【问题描述】:

使用 WPF,我有一个 ListBox 控件,里面有一个 DataTemplate。相关的 XAML 代码如下所示:

<ListBox Name="_todoList" Grid.Row="1" BorderThickness="2"
     Drop="todoList_Drop" AllowDrop="True"
     HorizontalContentAlignment="Stretch"
     ScrollViewer.HorizontalScrollBarVisibility="Disabled"                 
     AlternationCount="2">
     <ListBox.ItemTemplate>
         <DataTemplate>
             <Grid Margin="4">
                 <Grid.ColumnDefinitions>
                     <ColumnDefinition Width="Auto" />
                     <ColumnDefinition Width="*" />
                 </Grid.ColumnDefinitions>
                 <CheckBox Grid.Column="0" Checked="CheckBox_Check" />
                 <TextBlock Name="descriptionBlock"
                            Grid.Column="1"
                            Text="{Binding Description}"
                            Cursor="Hand" FontSize="14"
                            ToolTip="{Binding Description}"
                            MouseDown="TextBlock_MouseDown" />                      
             </Grid>
         </DataTemplate>
     </ListBox.ItemTemplate>
</ListBox>

我要做的是让TextBlock 响应(双击)将其变成TextBox。然后用户可以编辑描述,然后按回车键或更改焦点进行更改。

我尝试在与 TextBlock 相同的位置添加 TextBox 元素并使其可见 Collapsed,但当用户单击 @ 时,我不知道如何导航到右侧 TextBox 987654329@。也就是说,我知道用户点击了某个TextBlock,现在哪个我要显示TextBox

任何帮助将不胜感激,

-Ko9

【问题讨论】:

  • 作为提示,您可以直接将 XAML 粘贴到编辑器中,然后使用 101010 按钮将其格式化为代码,而不是使用 pre 标记和显式转义尖括号。
  • 感谢您以一种基本且非常全面的方式展示了 ListBox 中的 DataTemplate 的工作原理!

标签: c# wpf xaml listbox datatemplate


【解决方案1】:

我在这些情况下所做的是使用 XAML 层次结构来确定要显示/隐藏的元素。类似的东西:

<Grid>
  <TextBlock MouseDown="txtblk_MouseDown" />
  <TextBox LostFocus="txtbox_LostFocus" Visibility="Collapsed" />
</Grid>

用代码:

protected void txtblk_MouseDown(object sender, MouseButtonEventArgs e)
{
    TextBox txt = (TextBox)((Grid)((TextBlock)sender).Parent).Children[1];
    txt.Visibility = Visibility.Visible;
    ((TextBlock)sender).Visibility = Visibility.Collapsed;
}

protected void txtbox_LostFocus(object sender, RoutedEventArgs e)
{
    TextBlock tb = (TextBlock)((Grid)((TextBox)sender).Parent).Children[0];
    tb.Text = ((TextBox)sender).Text;
    tb.Visibility = Visibility.Visible;
    ((TextBox)sender).Visibility = Visibility.Collapsed;
}

我总是将我将要重用的此类内容转换为UserControl,我可以向其中添加额外的错误处理,并保证Grid 将仅包含两个项目,并且它们的顺序将永不改变。

编辑:此外,将其转换为 UserControl 允许您为每个实例创建一个 Text 属性,因此您可以为每个实例命名并直接引用文本,而无需通过 ((TextBox)myGrid.Children[1]).Text 强制转换来获取当前值。这将使您的代码更加高效和干净。如果你把它做成一个 UserControl,你也可以命名 TextBlockTextBox 元素,所以根本不需要强制转换。

【讨论】:

  • 这听起来很奇怪,你能编辑你的答案吗...我过早地点击了,然后删除了我的投票,因为我不确定这是否是我所追求的。然后我再次尝试投赞成票,因为这是我所需要的,而 Stack Overflow 说除非经过编辑,否则我不能对这篇文章进行投票。因此请求。干杯,结果非常有用。
【解决方案2】:

参考Nathan Wheeler的代码sn-p,下面的代码是我昨天写的完整的UserControl源码。 特别是解决了绑定问题。 Nathan 的代码很容易理解,但需要一些帮助才能处理数据绑定文本。

ClickToEditTextboxControl.xaml.cs

public partial class ClickToEditTextboxControl : UserControl
{
    public ClickToEditTextboxControl()
    {
        InitializeComponent();
    }

    public string Text
    {
        get { return (string)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Text.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register("Text", typeof(string), typeof(ClickToEditTextboxControl), new UIPropertyMetadata());

    private void textBoxName_LostFocus(object sender, RoutedEventArgs e)
    {
        var txtBlock = (TextBlock)((Grid)((TextBox)sender).Parent).Children[0];

        txtBlock.Visibility = Visibility.Visible;
        ((TextBox)sender).Visibility = Visibility.Collapsed;
    }

    private void textBlockName_MouseDown(object sender, MouseButtonEventArgs e)
    {
        var grid = ((Grid) ((TextBlock) sender).Parent);
        var tbx = (TextBox)grid.Children[1];
        ((TextBlock)sender).Visibility = Visibility.Collapsed;
        tbx.Visibility = Visibility.Visible;

        this.Dispatcher.BeginInvoke((Action)(() => Keyboard.Focus(tbx)), DispatcherPriority.Render);
    }

    private void TextBoxKeyDown(object sender, KeyEventArgs e)
    {
        if (e == null)
            return;

        if (e.Key == Key.Return)
        {
            textBoxName_LostFocus(sender, null);
        }
    }
}

ClickToEditTextboxControl.xaml

<UserControl x:Class="Template.ClickToEditTextboxControl"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         Name="root"
         d:DesignHeight="30" d:DesignWidth="100">
<Grid>
    <TextBlock Name="textBlockName" Text="{Binding ElementName=root, Path=Text}" VerticalAlignment="Center" MouseDown="textBlockName_MouseDown" />
    <TextBox Name="textBoxName" Text="{Binding ElementName=root, Path=Text, UpdateSourceTrigger=PropertyChanged}" Visibility="Collapsed" LostFocus="textBoxName_LostFocus" KeyDown ="TextBoxKeyDown"/>
</Grid>
</UserControl>

最后,您可以在 XAML 中使用此控件,如下所示:

<Template1:ClickToEditTextboxControl Text="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" MinWidth="40" Height="23" />

请注意,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged 已设置。它可以改变每种类型的绑定值。

【讨论】:

  • 感谢您提供此解决方案,我通过 DataTemplate 将它用于我的 ListBox,但新值不会在源中更新。 ItemsSource 的类型为 ObservableCollection&lt;string&gt;。有什么想法吗?
【解决方案3】:

理想的方法是创建一个ClickEditableTextBlock 控件,该控件默认呈现为一个TextBlock,但在用户单击它时显示一个TextBox。因为任何给定的 ClickEditableTextBlock 只有一个 TextBlock 和一个 TextBox,所以您不会遇到匹配问题。然后在 DataTemplate 中使用 ClickEditableTextBlock 而不是单独的 TextBlocks 和 TextBoxes。

这具有将功能封装在控件中的附带好处,因此您不会因编辑行为污染主窗口代码隐藏,而且您可以轻松地在其他模板中重用它。


如果这听起来太费力,您可以使用 Tag 或附加属性将每个 TextBlock 与 TextBox 关联:

<DataTemplate>
  <StackPanel>
    <TextBlock Text="whatever"
               MouseDown="TextBlock_MouseDown"
               Tag="{Binding ElementName=tb}" />
    <TextBox Name="tb" />
  </StackPanel>
</DataTemplate>

注意在标签上使用{Binding ElementName=tb} 来引用名为tb 的文本框。

在你的代码隐藏中:

private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e)
{
  FrameworkElement textBlock = (FrameworkElement)sender;
  TextBox editBox = (TextBox)(textBlock.Tag);
  editBox.Text = "Wow!";  // or set visible or whatever
}

(为了避免使用讨厌的 Tag 属性,您可以定义一个自定义附加属性来承载 TextBox 绑定,但为简洁起见,我没有显示。)

【讨论】:

    【解决方案4】:

    如果我可以补充,为了涵盖原始问题的 (double) 部分,在 Youngjae 的回复中,您在 xaml 文件中进行以下替换:

    <TextBlock Name="textBlockName" Text="{Binding ElementName=root, Path=Text}" VerticalAlignment="Center" MouseDown="textBlockName_MouseDown" />
    

    替换为

    <TextBlock Name="textBlockName" Text="{Binding ElementName=root, Path=Text}" VerticalAlignment="Center" >
        <TextBlock.InputBindings>
            <MouseBinding Gesture="LeftDoubleCLick" Command="{StaticResource cmdEditTextblock}"/>
        </TextBlock.InputBindings>
    </TextBlock>
    

    在 UserControl.Resources 中添加适当的 RoutedCommand

    <UserControl.Resources>
        <RoutedCommand x:Key="cmdEditTextblock"/>
    </UserControl.Resources>
    

    和 UserControl.CommandBindings 中的 CommandBinding

    <UserControl.CommandBindings>
        <CommandBinding Command="{StaticResource cmdEditTextblock}"
                        Executed="CmdEditTextblock_Executed"/>
    </UserControl.CommandBindings>
    

    也在代码隐藏文件中:

    private void textBlockName_MouseDown(object sender, MouseButtonEventArgs e)
    {
        var grid = ((Grid) ((TextBlock) sender).Parent);
        var tbx = (TextBox)grid.Children[1];
        ((TextBlock)sender).Visibility = Visibility.Collapsed;
        tbx.Visibility = Visibility.Visible;
        this.Dispatcher.BeginInvoke((Action)(() => Keyboard.Focus(tbx)), DispatcherPriority.Render);
    }
    

    被替换为

    private void CmdEditTextblock_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            var grid = ((Grid)((TextBlock)e.OriginalSource).Parent);
            var tbx = (TextBox)grid.Children[1];
            ((TextBlock)e.OriginalSource).Visibility = Visibility.Collapsed;
            tbx.Visibility = Visibility.Visible;
            this.Dispatcher.BeginInvoke((Action)(() => Keyboard.Focus(tbx)), DispatcherPriority.Render);
        }
    

    如果有些人想要左键双击作为输入手势,就像我做的那样......

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-09-25
      • 2010-09-28
      • 2018-10-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多