Встроенное редактирование TextBlock в списке с шаблоном данных (WPF)

используя 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. Затем пользователь может отредактировать описание и нажать клавишу return или изменить фокус, чтобы внести изменения.

Я пробовал добавлять TextBox элемент в том же положении, что и TextBlock и делает его видимым Collapsed, но я не знаю как перейдите вправо TextBox когда пользователь нажал на TextBlock. То есть, я знаю, что пользователь нажал на определенный TextBlock, Теперь , который TextBox показать?

любая помощь была бы очень признательна,

-Ko9

4 ответов


то, что я сделал в этих ситуациях, используется иерархией 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 будет содержать только два пункта, и порядок их никогда не изменится.

EDIT: кроме того, превращая это в UserControl позволяет создать Text свойство для каждого экземпляра, так что вы можете назвать каждый из них и ссылаться на текст непосредственно без рыбалки для текущего значения через ((TextBox)myGrid.Children[1]).Text литье. Это сделает ваш код более эффективным и чистым. Если вы сделаете это в UserControl, вы также можете назвать TextBlock и TextBox элементы, поэтому никакая отливка не необходима на всех.


обратитесь к фрагменту кода Натана Уилера, следующие коды являются полным источником UserControl, который я закодировал вчера. особенно, рассматриваются вопросы привязки. Код Натана легко следовать, но нуждается в некоторой помощи для работы с текстом базы данных.

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)
        {
            TextBoxLostFocus(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 установлен. Он позволяет изменять связанное значение в каждом типе.


идеальным способом сделать это было бы создать ClickEditableTextBlock control, который по умолчанию отображается как TextBlock, но показывает текстовое поле, когда пользователь щелкает его. Поскольку любой ClickEditableTextBlock имеет только один TextBlock и одно текстовое поле, у вас нет проблемы с соответствием. Затем вы используете ClickEditableTextBlock вместо отдельных текстовых блоков и текстовых полей в DataTemplate.

это имеет боковое преимущество инкапсуляции функциональности в элементе управления, поэтому вы не загрязняете свой код главного окна с поведением редактирования, а также вы можете легко использовать его в других шаблонах.


если это звучит как слишком много усилий, вы можете использовать тег или прикрепленное свойство, чтобы связать каждый 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
}

(чтобы избежать использования свойства nasty Tag, вы можете определить пользовательский прикрепленный свойство для переноса привязки TextBox, но для краткости я этого не показываю.)


если я могу дополнить, чтобы покрыть (двойной) часть исходного вопроса, в ответе 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>

добавление также правильного RoutedCommand в UserControl.Ресурсы

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

и CommandBinding в UserControl.CommandBindings

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

заменен 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);
    }

в случае, если некоторые люди хотят оставить doubleclick в качестве входного жеста, как я сделал...