views:

940

answers:

2

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

+3  A: 

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

itowlson
+2  A: 

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.

md5sum
this may sound odd, could you edit your answer... I clicked up prematurely, then removed my vote, because I wasnt sure if this was what I was after. I then tried to post an up vote again because it was what I needed and Stack Overflow says I cannot vote on this post unless it was edited. hence the request. Cheers, very useful post as it turned out.
Luke Duddridge