How to mark chosen item in treeview as a selected one(WPF)? - c#

I am new in c# and wpf. I have a treeveiew and what I need is - when user click on one of the items, mark this item as selected. For example as it is implemented in DataGrid when user click on the row it marks as selected and user can easaly understand which row was selected.
My implementation of TreeView is
<TreeView Name="Tv_request"
Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ItemsSource="{Binding Path=RequestSet}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate
ItemsSource="{Binding Childrens}"
DataType="{x:Type local:IRequest}">
<TreeViewItem MouseUp="TreeViewItem_MouseUp"
Header="{Binding TypePage}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
Way I can get clicked item - by TreeViewItem_MouseUp event, but I need to implement mark selection item.
How to implement it?
EDIT
In order to get clicked item I am using such approach
private void TreeViewItem_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
var tree = sender as TreeViewItem;
if (tree != null)
{
IRequest item = tree.DataContext as IRequest;
Presenter?.TreeViewSelectedItem(item);
}
}
Such way I can have access to my selected item, but if I change it to
private void TreeViewItem_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
var tree = sender as ContentPresenter;
if (tree != null)
{
IRequest item = tree.DataContext as IRequest;
Presenter?.TreeViewSelectedItem(item);
}
}
So, tree.DataContext return me NodeType and logically it is ok, because I set
<ContentPresenter Content="{Binding NodeType}"
MouseUp="TreeViewItem_MouseUp"/>
instead of
<TreeViewItem MouseUp="TreeViewItem_MouseUp"
Header="{Binding NodeType}"/>
So, question is, how I can get entire clicked object not just his NodeType?

Firstly, your item template is creating a TreeViewItem inside a TreeViewItem. It should look like this:
<HierarchicalDataTemplate
ItemsSource="{Binding Childrens}"
DataType="{x:Type local:IRequest}">
<ContentPresenter Content="{Binding TypePage}"/>
</HierarchicalDataTemplate>
The TreeViewItem will be automatically created by WPF, and the HierarchicalDataTemplate you provide will be applied to it.
Secondly, TreeViewItem has a property called IsSelected. You can use the Style to bind this property to a property of your IRequest object:
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True" />
<Setter Property="IsSelected" Value="{Binding RequestIsSelected}" />
</Style>
Or you can use an EventSetter to provide a handler for the TreeViewItem.Selected event if you prefer:
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True" />
<EventSetter RoutedEvent="Selected" Value="TreeViewItem_Selected" />
</Style>
You could also use the TreeView.SelectionChanged event instead of dealing with individual tree view items, but be aware that you (annoyingly!) can't select a new item from the TreeView itself.

If you don't mess things up by nesting a hierarchicaltemplate inside an itemtemplate you don't need to do anything at all to get a selected treeview item coloured.
Look at the xaml here:
https://learn.microsoft.com/en-us/dotnet/api/system.windows.hierarchicaldatatemplate?view=netcore-3.1
<HierarchicalDataTemplate DataType = "{x:Type src:League}"
ItemsSource = "{Binding Path=Divisions}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType = "{x:Type src:Division}"
ItemsSource = "{Binding Path=Teams}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type src:Team}">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
Notice what you give it as a template appears inside a treeviewitem. Not hierarchicaldatatemplate within itemtemplate.
Similarly, here: https://www.wpf-tutorial.com/treeview-control/treeview-data-binding-multiple-templates/
<TreeView Name="trvFamilies">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type self:Family}" ItemsSource="{Binding Members}">
<StackPanel Orientation="Horizontal">
<Image Source="/WpfTutorialSamples;component/Images/group.png" Margin="0,0,5,0" />
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" [" Foreground="Blue" />
<TextBlock Text="{Binding Members.Count}" Foreground="Blue" />
<TextBlock Text="]" Foreground="Blue" />
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type self:FamilyMember}">
<StackPanel Orientation="Horizontal">
<Image Source="/WpfTutorialSamples;component/Images/user.png" Margin="0,0,5,0" />
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" (" Foreground="Green" />
<TextBlock Text="{Binding Age}" Foreground="Green" />
<TextBlock Text=" years)" Foreground="Green" />
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
If you try that code in the last link, notice when you click a node - a family or member - you get a blue background.
This is how you mark a selected treeviewitem.
Don't mess up your templates and it "just works".
If you need to know which one was selected then this is a bit tricky because you can't just bind selecteditem like a datagrid. This is indirectly due to the hierarchical nature of the control meaning each node has it's own itemcontrol for children. A treeviewitem is a headereditemscontrol.
This answer illustrates one of the behaviors commonly used to enable binding of selecteditem:
Data binding to SelectedItem in a WPF Treeview
Once you bind the selected item like this you can then work with that property in the viewmodel. When it changes the setter will be hit and you can put code in there to do work with that item.

Related

WPF Set focus on text box when item added to Listbox

I have a listbox with an Add button mapped to a command backed by a SelectedItem property in the VM.
When an item is added to the listbox i have the SelectedItem set to the new item so it has focus in the listbox. I'd like to have a textbox (data entry for that new item) to have focus.
I've been looking at event triggers but i havent seen a way to cross items but basically I think what i want is an event trigger for the listbox selection change to to set the focus on a text box.
how would i go about doing this?
As an example I have the following XAML code. This will add a Person (name and age property only)
Basically I want the txtName textbox to have focus when an item is selected in the listbox.
<StackPanel>
<TextBlock>Name</TextBlock>
<TextBox Text="{Binding NewPerson}"></TextBox>
<Button Command="{Binding AddPersonDelegateCommand}">Add</Button>
<Button>Remove</Button>
<ListBox ItemsSource="{Binding People}" DisplayMemberPath="Name"
SelectedItem="{Binding SelectedPerson}">
</ListBox>
<TextBox Name="txtName" Text="{Binding SelectedPerson.Name, Mode=TwoWay}"</TextBox>
<TextBox Name="txtAge" Text="{Binding SelectedPerson.Age, Mode=TwoWay}"></TextBox>
</StackPanel>
here is the xaml based trigger to set focus on the txtName TextBox when SelectedItem property is toggled from null
so idea here is that you set SelectedPerson property to null followed by the instance of newly created person object, that should do the trick and will set the focus to the desired TextBox
the limitation of this trigger is that you need to set SelectedPerson property null before you set to the new object, an attached behavior can solve that issue too, if this is not workable for you.
<StackPanel>
<TextBlock>Name</TextBlock>
<TextBox Text="{Binding NewPerson}"></TextBox>
<Button Command="{Binding AddPersonDelegateCommand}">Add</Button>
<Button>Remove</Button>
<ListBox DisplayMemberPath="Name"
x:Name="list"
SelectedItem="{Binding SelectedPerson}">
</ListBox>
<TextBox Name="txtName"
Text="{Binding SelectedPerson.Name, Mode=TwoWay}">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="FocusManager.FocusedElement"
Value="{Binding RelativeSource={RelativeSource Self}}" />
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem,ElementName=list}"
Value="{x:Null}">
<Setter Property="FocusManager.FocusedElement"
Value="{x:Null}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox Name="txtAge"
Text="{Binding SelectedPerson.Age, Mode=TwoWay}"></TextBox>
</StackPanel>

Disallow/Block selection of disabled combobox item in wpf

I'm writing an application wherein I would like to disable few items in the ComboBox and also want to disallow/block selection of disabled items. Please note ComboBox in main window has another ComboBox as ComboBox Item init (that is decided at run time by DataTemplateSelector).
With below code I'm able to disable a ComboBox within ComboBox but it would not stop user from selecting that disabled ComboBox item. Any help in disallow/block selection of disabled items would be helpful.
Below are the code snippets
ComboBox in main window:
<Grid>
<ComboBox HorizontalAlignment="Left" VerticalAlignment="Top"
Width="120" Margin="87.2,44.8,0,0"
ItemsSource="{Binding Cars}"
ItemsPanel="{DynamicResource ItemsPanelTemplateHorizontal}"
ItemTemplateSelector="{StaticResource QualityComboBoxTemplateSelector}"
SelectedItem="{Binding SelectedItm}"/>
</Grid>
Data template selector:
public class QualityComboBoxTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var element = container as FrameworkElement;
var dataTemplate = element.FindResource(((item is string) && item.Equals("Ferrari")) ?
"DataTemplateTopLevelCombobox2" : "DataTemplateTopLevelCombobox1") as DataTemplate;
return dataTemplate;
}
}
Data templates for above ComboBox:
<DataTemplate x:Key="DataTemplateTopLevelCombobox1">
<Border BorderBrush="Black" BorderThickness="1" >
<TextBlock HorizontalAlignment="Left"
TextWrapping="Wrap" Text="{Binding}"
VerticalAlignment="Top"/>
</Border>
</DataTemplate>
<DataTemplate x:Key="DataTemplateTopLevelCombobox2">
<Border Width="100">
<ComboBox Text="Custom" Height="21.96"
ItemsSource="{Binding DataContext.Models, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
IsEnabled="{Binding DataContext.EnableCombo, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
</Border>
</DataTemplate>
You can achieve this by setting IsEnabled property of a ComboBoxItem to false;
So each item in ComboBox's ItemSource (i.e. Cars in your case) can be an object having some property (say IsSelectable) specifying whether it should be enabled or disabled and then use it with a style to make an item un-selectable. something like this -
<Style TargetType="ComboBoxItem">
<Setter Property="IsEnabled" Value="{Binding IsSelectable}"/>
</Style>
Update:
<Grid>
<ComboBox
Width="120"
Margin="87.2,44.8,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ItemTemplateSelector="{StaticResource QualityComboBoxTemplateSelector}"
ItemsPanel="{DynamicResource ItemsPanelTemplateHorizontal}"
ItemsSource="{Binding Cars}"
SelectedItem="{Binding SelectedItm}">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter
Property="IsEnabled"
Value="{Binding IsSelectable}" />
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</Grid>
To solve the problem pointed by #JordyBoom.
ItemsContainerGenerator does not generate items until dropdown is opened at least once.
So if you open the drop down and close it again in window’s loaded event handler then all supposed to work fine with mouse as well as with keyboard selection.
public MainWindow()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(onLoaded);
}
private void onLoaded(object sender, RoutedEventArgs e)
{
cmbx.IsDropDownOpen = true;
cmbx.IsDropDownOpen = false;
}
source: WPF: Making combo box items disabled – also when accessed using the keyboard

Access a control from within a DataTemplate with its identifying name

In my WPF application I have a ComboBox control that is located inside a Grid Control. In XAML I am assigning a name to the ComboBox:
<DataGridTemplateColumn Header="Status">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock VerticalAlignment="Center" Text="{Binding name_ru}" Width="Auto" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox Name="stcom" Style="{DynamicResource ComboBoxStyle}" SelectionChanged="status_SelectionChanged" Height="auto" Width="Auto">
<ComboBox.BorderBrush>
<SolidColorBrush Color="{DynamicResource Color1}"/>
</ComboBox.BorderBrush>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
With the method FindName(string) I am trying to refer to the ComboBox with its associated name:
ComboBox stcom
{
get
{
return (ComboBox)FindName("stcom");
}
}
if (stcom != null)
{
stcom.ItemsSource = list;
}
But obviously the control can not be found because the reference stcom remains null.
The question now is how to refer to my ComboBox using its name property ?
The answer is:
<Style x:Key="CheckBoxStyle1" TargetType="{x:Type CheckBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<StackPanel Orientation="Horizontal">
<Grid>
<TextBlock Name="tbUserIcon" Text="t1" />
<TextBlock Name="tbCheck" Text="✓" />
</Grid>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
and C#:
checkBox.ApplyTemplate();
var tbUserIcon= (TextBlock)checkBox.Template.FindName("tbUserIcon", checkBox);
don't forget the checkBox.ApplyTemplate() be fore Template.FindName() it's important!
First you have to get access to the control template which it has been applied to, then you can find an element of the template by name.
Have a look at the MSDN knowledge base :
How to: Find ControlTemplate-Generated Elements
You can't access controls that are part of a DataTemplate with their name.
You can try to read about some workarounds for example
WPF - Find a Control from DataTemplate in WPF
You can also have a look at the dozens of posts here on SO issuing this topic for example
here
here
here
here
here
here
here
here

how to switch views according to command

I've a main view and many usercontrols.
The main view contains a two column grid, with the first column filled with a listbox whose datatemplate consists of a usercontrol and the second column filled with another usercontrol. These two usercontrols have the same datacontext.
MainView:
<Grid>
//Column defs
...
<ListView Grid.Column="0" ItemSource="{Binding FooList}">
...
<DataTemplate>
<Views: FooView1 />
</DataTemplate>
</ListView>
<TextBlock Text="{Binding FooList.Count}" />
<StackPanel Grid.Column="1">
<Views: FooView2 />
</StackPanel>
<Grid>
FooView1:
<UserControl>
<TextBlock Text="{Binding Foo.Title}">
</UserControl>
FooView2:
<UserControl>
<TextBlock Text="{Binding Foo.Detail1}">
<TextBlock Text="{Binding Foo.Detail2}">
<TextBlock Text="{Binding Foo.Detail3}">
<TextBlock Text="{Binding Foo.Detail4}">
</UserControl>
I've no IDE here. Excuse me if there is any syntax error
When the user clicks on a button. These two usercontrols have to be replaced by another two usercontrols, so the datacontext changes, the main ui remaining the same.
ie, FooView1 by BarView1 and FooView2 by BarView2
In short i want to bind this view changes in mainview to my command (command from Button)
How can i do this?
Also tell me if i could merge the usercontrol pairs, so that only one view exists for each viewmodel
ie, FooView1 and FooView2 into FooView and so on...
Though I am still not sure whether I got you, I suggest the following.
In your example it looks like you want to show the details of the Foo object. The datacontext stays the same. Maybe you can set Visibility-Flags in your viewmodel to decide what you want to display. This can be done using the command that is executed by your button.
FooView:
<UserControl>
<TextBlock Text="{Binding Foo.Title}"
Visibility="{Binding ShowTitle}">
<TextBlock Text="{Binding Foo.Detail1}"
Visibility="{Binding ShowDetails}">
<TextBlock Text="{Binding Foo.Detail2}"
Visibility="{Binding ShowDetails}">
<TextBlock Text="{Binding Foo.Detail3}"
Visibility="{Binding ShowDetails}">
<TextBlock Text="{Binding Foo.Detail4}"
Visibility="{Binding ShowDetails}">
</UserControl>
Is this a possible solution?
If you want to change the datatemplate you might do something like this:
FooView (2nd version)
<UserControl>
<UserControl.Resources>
<DataTemplate x:Key="dataTemplate1">
<TextBlock Text="{Binding Foo.Title}">
</DataTemplate>
<DataTemplate x:Key="dataTemplate2">
<TextBlock Text="{Binding Foo.Detail1}">
<TextBlock Text="{Binding Foo.Detail2}">
<TextBlock Text="{Binding Foo.Detail3}">
<TextBlock Text="{Binding Foo.Detail4}">
</DataTemplate>
</UserControl.Resources>
<Style x:Key="Default">
<Style.Triggers>
<DataTrigger Binding="{Binding Foo.ShowFooView}" Value="1" >
<Setter Property="Template" Value="{StaticResource dataTemplate1}" />
<DataTrigger>
<DataTrigger Binding="{Binding Data.ShowFooView}" Value="2" >
<Setter Property="Template" Foo="{StaticResource dataTemplate2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl>
The idea is, that depending on the property Foo.ShowFooView you decide which data template should be used. Of course the type Foo should implement INotifyPropertyChanged to notify the UI.
Sorry, but I don't get the point. To answer your question you need to provide more information.
Do you want to change your datacontext by replacing a usercontrol?
Why don't you use different datatemplates for different target types?
Maybe you can provide some code snippets.

Can I automatically wrap HierarchicalDataTemplate items inside of a TreeViewItem?

I have a 4-level tree structure, defined by:
<HierarchicalDataTemplate DataType="{x:Type src:Level1}" ItemsSource="{Binding Path=Level2Items}">
<TextBlock Text="{Binding Path=Level1Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type src:Level2}" ItemsSource="{Binding Path=Level3Items}">
<TextBlock Text="{Binding Path=Level2Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type src:Level3}" ItemsSource="{Binding Path=Level4Items}">
<TextBlock Text="{Binding Path=Level3Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type src:Level4}">
<TextBlock Text="{Binding Path=Level4Name}"/>
</DataTemplate>
And it works great. The only thing is, I can't programmatically select any of my bound items, because they're not of type TreeViewItem (and therefore don't have the "IsSelected" property). Is there a way to automatically wrap databound items in a particular container type (in this case: TreeViewItem)?
If your items are in a TreeView, they'll be wrapped in a TreeViewItem automatically by the TreeView's ItemContainerGenerator. You can do something like this to ensure the IsSelected property on TreeViewItem maps to a property on your data class:
<TreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding MyIsSelectedProperty}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>

Categories