2
votes

I'm trying to create a TreeView which allows the user to rename the nodes in the TreeView. The tree represents an HL7 message and is structured from segment to subcomponent hierarchically.

For example:

PID
   PID.1
   PID.2
   etc...

I need to allow the user to select a node, press F2 to put the node into edit mode. Because HL7 allows repeating message structures, I also need the SelectedItem so I can know which node was changed in case duplicate names exist.

Currently, each node is a TextBox with IsReadOnly set to true and is stylized to look like a TextBlock. When the user presses F2, I stylize the TextBox to look like it normally does for input. The problem is, the TextBox is eating all the mouse events preventing the TreeView from setting SelectedItem or raising SelectedItemChanged.

I found some discussion on MSDN where one person says use the PreviewMouseLeftButtonDown event on the TextBox. I'm using that and the TextBox is still consuming the event.

Has anyone run into this before or have any suggestions?

1
Found the solution. I'm surprised I haven't been able to find this anywhere yet. Anyways, if you are using binding, handle the GotFocus or PreviewMouseLeftButtonDown events, cast the sender as TextBox into a local object. From that you can access the DataContext member of the textbox object. This will represent the data object bound to the TreeViewItems. - Josh

1 Answers

1
votes

Another way is to have a TextBlock for display and a hidden TextBox for editing. Listen for F2 on the TreeView which will receive the keyboard events since the TextBox won't be getting any input focus while it is hidden. When F2 is pressed, hide the TextBlock and show the TextBox for editing. Handle the LostFocus event on the TextBox to hide the TextBox and show the TextBlock again.

One advantage of doing it this way is you don't have to fake a TextBox into looking and behaving like a TextBlock. The TextBlock will always look and behave like a TextBlock and the TextBox will always look and behave like a TextBox, and they will each be able to inherit any styling applied at a higher resource level.

Edit: Adding some sample code.

Here is the XAML:

<Window.Resources>

    <Style x:Key="TreeViewTextBlockStyle" TargetType="TextBlock">
        <Setter Property="Text" Value="{Binding DisplayText}"/>
        <Style.Triggers>
            <DataTrigger Binding="{Binding InEditMode}" Value="true">
                <Setter Property="Visibility" Value="Collapsed"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>

    <Style x:Key="TreeViewTextBoxStyle" TargetType="TextBox">
        <Setter Property="Text" Value="{Binding DisplayText, Mode=TwoWay}"/>
        <Setter Property="MinWidth" Value="50"/>
        <EventSetter Event="LostFocus" Handler="TreeViewTextBox_LostFocus" />
        <Style.Triggers>
            <DataTrigger Binding="{Binding InEditMode}" Value="false">
                <Setter Property="Visibility" Value="Collapsed"/>
            </DataTrigger>
            <DataTrigger Binding="{Binding InEditMode}" Value="true">
                <Setter Property="FocusManager.FocusedElement" Value="{Binding RelativeSource={RelativeSource Mode=Self}}"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>

    <HierarchicalDataTemplate x:Key="HL7MessageTemplate" ItemsSource="{Binding Segments}">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="[msg]--" FontWeight="Bold"/>
            <TextBlock Style="{StaticResource TreeViewTextBlockStyle}"/>
            <TextBox Style="{StaticResource TreeViewTextBoxStyle}" />
        </StackPanel>
    </HierarchicalDataTemplate>

    <DataTemplate x:Key="HL7SegmentTemplate">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="[seg]--" FontWeight="Bold"/>
            <TextBlock Style="{StaticResource TreeViewTextBlockStyle}"/>
            <TextBox Style="{StaticResource TreeViewTextBoxStyle}" />
        </StackPanel>
    </DataTemplate>

    <HierarchicalDataTemplate x:Key="HL7SegmentWithSubcomponentsTemplate" ItemsSource="{Binding Subcomponents}">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="[seg+sub]--" FontWeight="Bold"/>
            <TextBlock Style="{StaticResource TreeViewTextBlockStyle}"/>
            <TextBox Style="{StaticResource TreeViewTextBoxStyle}" />
        </StackPanel>
    </HierarchicalDataTemplate>

    <DataTemplate x:Key="HL7SubcomponentTemplate">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="[sub]--" FontWeight="Bold"/>
            <TextBlock Style="{StaticResource TreeViewTextBlockStyle}"/>
            <TextBox Style="{StaticResource TreeViewTextBoxStyle}" />
        </StackPanel>
    </DataTemplate>

    <local:HL7DataTemplateSelector x:Key="HL7DataTemplateSelector"/>

</Window.Resources>    
<Grid>
    <TreeView Name="treeView1" ItemsSource="{Binding}" ItemTemplateSelector="{StaticResource HL7DataTemplateSelector}" KeyUp="treeView1_KeyUp"/>
</Grid>

Here is the code behind:

private void treeView1_KeyUp(object sender, KeyEventArgs e)
{
    if (e.Key == Key.F2)
    {
        HL7Object selectedHL7Object = treeView1.SelectedItem as HL7Object;
        if (selectedHL7Object != null)
        {
            selectedHL7Object.InEditMode = true;
        }
    }
}

private void TreeViewTextBox_LostFocus(object sender, RoutedEventArgs e)
{
    HL7Object selectedHL7Object = treeView1.SelectedItem as HL7Object;
    if (selectedHL7Object != null)
    {
        selectedHL7Object.InEditMode = false;
    }
}

This code assumes your HL7Object is the base class for your data objects, such as the following:

public class HL7Object : INotifyPropertyChanged
{
    private string DisplayTextField;
    public string DisplayText
    {
        get { return this.DisplayTextField; }
        set
        {
            if (this.DisplayTextField != value)
            {
                this.DisplayTextField = value;
                this.OnPropertyChanged("DisplayText");
            }
        }
    }

    private bool InEditModeField = false;
    public bool InEditMode
    {
        get { return this.InEditModeField; }
        set
        {
            if (this.InEditModeField != value)
            {
                this.InEditModeField = value;
                this.OnPropertyChanged("InEditMode");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

And also that you have implemented a DataTemplateSelector, which I assume you have because of your complex requirements. If not, here is an example:

public class HL7DataTemplateSelector : DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        FrameworkElement element = container as FrameworkElement;

        if (element != null && item != null &&
            (item is HL7Message || item is HL7Segment || item is HL7Subcomponent)
            )
        {
            HL7Message message = item as HL7Message;
            if (message != null)
            {
                return element.FindResource("HL7MessageTemplate") as DataTemplate;
            }

            HL7Segment segment = item as HL7Segment;
            if (segment != null)
            {
                if (segment.Subcomponents != null && segment.Subcomponents.Count > 0)
                {
                    return element.FindResource("HL7SegmentWithSubcomponentsTemplate") as DataTemplate;
                }
                else
                {
                    return element.FindResource("HL7SegmentTemplate") as DataTemplate;
                }
            }

            HL7Subcomponent subcomponent = item as HL7Subcomponent;
            if (subcomponent != null)
            {
                return element.FindResource("HL7SubcomponentTemplate") as DataTemplate;
            }
        }

        return null;
    }
}