Встроенное редактирование TextBlock в ListBox с шаблоном данных (WPF) - PullRequest
9 голосов
/ 18 января 2010

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

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

Любая помощь будет принята с благодарностью,

-Ko9

Ответы [ 4 ]

15 голосов
/ 18 января 2010

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

13 голосов
/ 07 октября 2011

Обратитесь к фрагменту кода Натана Уилера, следующие коды - полный исходный код 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)
        {
            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 установлено. Это позволяет изменять связанное значение в каждом типе.

4 голосов
/ 18 января 2010

Идеальный способ сделать это - создать элемент управления ClickEditableTextBlock, который по умолчанию отображается как TextBlock, но показывает TextBox, когда пользователь щелкает по нему. Поскольку у любого данного ClickEditableTextBlock есть только один TextBlock и один TextBox, у вас нет проблем с соответствием. Затем вы используете 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, но для краткости я этого не показываю.)

1 голос
/ 28 сентября 2018

Если я могу дополнить, чтобы покрыть часть (двойную) исходного вопроса, в ответе Янгджа вы сделаете следующую замену в файле 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.Resources

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

и привязка команд в 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);
}

заменяется на

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

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

...