使用数据模板 (WPF) 在列表框中内联编辑 TextBlock

发布于 2024-08-18 10:37:16 字数 1592 浏览 8 评论 0原文

使用 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 当用户单击 TextBlock 时。也就是说,我知道用户已单击某个 TextBlock,现在我要显示哪个 TextBox

任何帮助将不胜感激,

-Ko9

Using WPF, I have a ListBox control with a DataTemplate inside it. The relevant XAML code is shown below:

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

What I am trying to do is make the TextBlock respond to a (double)click which turns it into a TextBox. The user can then edit the description, and press return or change focus to make the change.

I have tried adding a TextBox element in the same position as the TextBlock and making its visiblity Collapsed, but I don't know how to navigate to the right TextBox when the user has clicked on a TextBlock. That is, I know the user has clicked on a certain TextBlock, now which TextBox do I show?

Any help would be appreciated greatly,

-Ko9

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

白龙吟 2024-08-25 10:37:16

在这些情况下,我所做的就是使用 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 元素,因此根本不需要转换。

What I've done in these situations is used the XAML hierarchy to determine which element to show/hide. Something along the lines of:

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

with the code:

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

I always turn stuff like this that I'm going to reuse into a UserControl, which I can add additional error handling to, and guarantee that the Grid will only contain two items, and the order of them will never change.

EDIT: Additionally, turning this into a UserControl allows you to create a Text property for each instantiation, so you can name each one and reference the text directly without fishing for the current value through the ((TextBox)myGrid.Children[1]).Text casting. This will make your code much more efficient and clean. If you make it into a UserControl, you can also name the TextBlock and TextBox elements, so no casting is needed at all.

不语却知心 2024-08-25 10:37:16

参考Nathan Wheeler的代码片段,以下代码是我昨天编写的完整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。它可以更改每种类型中的绑定值。

Refer to the Nathan Wheeler's code snippet, the following codes are complete UserControl source that I coded yesterday.
Especially, Binding issues are addressed.
Nathan's code is easy to follow, but needs some aid in order to work with databound text.

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>

And, finally, you can use this control in the XAML as below:

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

Note that Mode=TwoWay, UpdateSourceTrigger=PropertyChanged is set. It enables to change the binded value in every type.

提笔落墨 2024-08-25 10:37:16

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

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


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

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

请注意在 Tag 上使用 {Binding ElementName=tb} 来引用名为的 TextBox待定。

在您的代码隐藏中:(

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 绑定,但为了简洁起见,我没有显示这一点。)

The ideal way to do this would be to create a ClickEditableTextBlock control, which by default renders as a TextBlock but shows a TextBox when the user clicks it. Because any given ClickEditableTextBlock has only one TextBlock and one TextBox, you don't have the matching issue. Then you use a ClickEditableTextBlock instead of separate TextBlocks and TextBoxes in your DataTemplate.

This has the side benefit of encapsulating the functionality in a control so you don't pollute your main window code-behind with the edit behaviour, plus you can easily reuse it in other templates.


If this sounds like too much effort, you can use Tag or an attached property to associate each TextBlock with a TextBox:

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

Note the use of {Binding ElementName=tb} on the Tag to refer to the TextBox named tb.

And in your code-behind:

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
}

(To avoid the use of the nasty Tag property, you could define a custom attached property to carry the TextBox binding, but for brevity I'm not showing that.)

美煞众生 2024-08-25 10:37:16

如果我可以补充一下,为了涵盖原始问题的 (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 中添加正确的 RoutedCommand。 UserControl.CommandBindings 中的资源

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

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

如果有些人想要将左双击作为输入手势,就像我一样。

If I may supplement, in order to cover the (double) part of the original question, in Youngjae's reply you make the following replacement in the xaml file:

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

is replaced with

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

adding also the proper RoutedCommand in UserControl.Resources

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

and a CommandBinding in UserControl.CommandBindings

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

Also in the code behind file:

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

is replaced by

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

In case some people want to have left doubleclick as input gesture, like I did.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文