Attach commands to TreeView in wpf using prism - wpf

How do I use a DelegateCommand in a TreeView to get the Expanded event?
Should I be using the DelegateCommand or is there another way?
Thanks

Since you are mentioning Prism, I assume you have a controller or ViewModel attached to the view containing your TreeView...
That being the case, expose a boolean property IsExpanded
private bool _isExpanded;
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (value != _isExpanded)
{
_isExpanded = value;
RaisePropertyChanged("IsExpanded");
// Apply custom logic here...
}
}
}
Now to hook this property up to the TreeView, you need to apply the following style in the TreeView's resources (or further up the Visual tree as appropriate)
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding Path=IsExpanded, Mode=TwoWay}" />
</Style>
NB: You can also use a similar technique to hook up the IsSelected property - also very useful!!

Related

Bind IsExpaned to my own custom TreeViewItem in Wpf?

I want to bind my custom TreeViewItem to IsExpanded.
The normal way without a custom TreeView Item would look like this.
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded}"></Setter>
</Style>
</TreeView.ItemContainerStyle>
But I want to bind it to my own TreeViewItem for example named CoolTreeItemModel.
CoolTreeItemModel could look like this:
public class CoolTreeItemModel : XY
{
public LocalTreeItemModel()
{
TreeViewItems = new List<CoolTreeItemModel>();
}
public List<CoolTreeItemModel> TreeViewItems { get; set; }
public SomeType IsValid { get; set; }
public bool IsExpanded { get; set; }
}
How CoolTreeItemModel is bound:
<TreeView HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding TreeRoots}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="models:CoolTreeItemModel"
ItemsSource="{Binding TreeViewItems}"></HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
How can I bind to the IsExpanded property of CoolTreeItemModel ?
Thanks for your help.
The "normal way" is applicable in this case. Each CoolTreeItemModel will be implicitly wrapped in a TreeViewItem container so you should be able to bind to your IsExpanded property. You may want to set the Mode of the binding to TwoWay though:
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"></Setter>
</Style>
</TreeView.ItemContainerStyle>
Binding requires the use of dependency properties. Dependency Properties On MSDN You would need to define a dependency property like so:
public ClassName
{
public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register("IsExpanded", typeof(bool), typeof(ClassName));
public bool IsExpanded
{
get { return (bool)GetValue(IsExpandedProperty); }
set { SetValue(IsExpandedProperty, value); }
}
}
From there you can then include the namespace of your class in the top of your xaml and then bind to IsExpanded like normal
<MyNamespace:ClassName IsExpanded="{Binding Value}" />
I Would like to raise one concern of my though. When developers new to XAML/WPF start developing their own controls, the distinction between view data and business data gets muddled. If this is your own control to be consumed by others, there is an entire style template that may need to be created and used. All of this binding should be taking place in this style template, following the example set forth by Microsoft. Also a custom control should have no view model or defined data context as it is expected to be provided by the consumer, meaning a lot of your logic is going to be in the code behind, which is okay as a custom control is only view related and shouldn't have any business logic in it.

Why are my TreeViewItems acting like RadioButtons?

I have a WPF TreeView for which I've implemented a small model class behind the scenes. I bind a list of them to the TreeView's ItemsSource when creating the control. (I've pared the code here down a bit for the sake of simplicity, but it should be reproducable.)
public class TreeViewItemModel
{
public ObservableCollection<TreeViewItemModel> Children { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
public TreeViewItemModel()
{
Children = new ObservableCollection<TreeViewItemModel>();
IsSelected = false;
}
}
public partial class MainWindow : Window
{
public ObservableCollection<TreeViewItemModel> MyTree { get; set; }
public MainWindow()
{
InitializeComponent();
// Add some dummy values
List<TreeViewItemModel> items = new List<TreeViewItemModel>();
for (int i = 0; i < 10; i++) items.Add(new TreeViewItemModel() { Name = ("Node" + i) });
MyTree = new ObservableCollection<TreeViewItemModel>(items);
DataContext = this;
}
}
My TreeViewItems themselves contain checkboxes. Now, what I'd like to do is to bind IsSelected to the checkbox so that at the end of the day I (hopefully) have a list of TreeViewItemModel classes with IsSelected set to whether or not the checkbox is checked.
To that end, I have this style:
<Style x:Key="{x:Type TreeViewItem}" TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
and this TreeView declaration:
<TreeView ItemsSource="{Binding MyTree}" >
<TreeView.Resources>
<DataTemplate DataType="{x:Type UI:TreeViewItemModel}">
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsSelected}" />
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
This almost works. I can create a list of items programmatically and they get bound to the TreeView, check off items in my TreeView, and when I check them in C# IsSelected is set appropriately.
Except for one thing: my TreeViewItems all act like RadioButtons. I click one, and it sets IsSelected to true. I rejoice! But then I click on another... and it deselects the first TreeViewItem! I can never have more than one selected at a time.
But... why?! I don't understand at all. They're all bound to different items on the backend, so why would setting IsSelected change the state of another item?
:'(
In your Style for TreeViewItem you bind TreeViewItem.IsSelected to IsSelected property of your view model which basically means that CheckBox will be checked if TreeViewItem is selected. It happens so because WPF TreeView does not support multi selection.
You can easily add multi selection by changing TreeViewItem content into CheckBox or ToggleButton, exactly what you're trying to achieve, but then you cannot bind TreeViewItem.IsSelected to your view model.
What currently happens is
you click to select one item
previous TreeViewItem.IsSelected is set to false
this is passed to your view model by IsSelected
which is then passed back to CheckBox.IsChecked
new TreeViewItem.IsSelected is set to true
and so on
Remove Style for TreeViewItem and leave only CheckBox.IsChecked to IsSelected binding
On a side note you don't need StackPanel when you want to show just one element like CheckBox
You try removing your style? You should then see multiple selections

wpf TreeView - How to call function on IsExpanded property change

Is there a way in XAML to call a function when the TreeViewItem property IsExpanded changes?
I believe the not so good alternative would be to loop through all TreeViewItems and do an item.IsExpanded += handler call if I understand things correctly.
Or I could check for clicks on the expander element I guess.
What I'm doing is persisting the expand/collapse state of the tree. Please answer the first question before suggesting alternative ways to persist this just to edify me on properties and xaml.
Building on Joel's answer, you can use EventSetters in the TreeViewItem Style which refer to event handlers in your code-behind:
<TreeView ... >
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem" >
<EventSetter Event="TreeViewItem.Expanded" Handler="OnTreeExpanded" />
<EventSetter Event="TreeViewItem.Collapsed" Handler="OnTreeCollapsed" />
</Style>
</TreeView.ItemContainerStyle>
...
Code-behind - normal event handlers:
private void OnTreeExpanded(object sender, RoutedEventArgs e)
{
var tvi = (TreeViewItem)sender;
...
e.Handled = true;
}
private void OnTreeCollapsed(object sender, RoutedEventArgs e)
{
var tvi = (TreeViewItem)sender;
...
e.Handled = true;
}
Note: Make sure you set e.Handled = true in the event handlers, or else you'll get events from all parents of the current TreeViewItem as well..
I would bind the IsExpanded property of the TreeViewItem to my model using something like:
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded}" />
</Style>
</TreeView.ItemContainerStyle>
Then I can run thru the model and get the value for IsExpanded and save it. Additionally, when restoring, simply set the IsExpanded property.
Since you need to call other code when changed, implement IsExpanded like so:
private bool _IsExpanded;
public bool IsExpanded
{
get { return _IsExpanded; }
set
{
if (_IsExpanded == value) return;
_IsExpanded = value;
NotifyPropertyChanged( "IsExpanded" );//or however you need to do it
CallSomeOtherFunc();//this is the code that you need to be called when changed.
}
}

Update the Datagrid Column Header properties through styles

In my application i need to change the properties related to a datagrid header like ColumnHeader Font, fontsize etc. As there is no single property for the same currently, i am updating this through Style setters. But the problem is for a single property change(like FontSize) i have to create an entire collection of the SetterBase and update the single property along with the other propertied in the setterbase collection. Is there any other way to update a property as in this scenario.
Code snippet:
set
{
Style m_ColumnHeaderStyle = new Style(typeof(DataGridColumnHeader));
m_ColumnHeaderStyle.Setters.Add(m_ColumnFontWeightProperty);
m_ColumnHeaderStyle.Setters.Add(m_ColumnFontSizeProperty);
m_ColumnHeaderStyle.Setters.Add(m_ColumnFontItalicProperty);
m_ColumnFont = new Setter(DataGridColumnHeader.FontFamilyProperty, new FontFamily(value));
m_ColumnHeaderStyle.Setters.Add(m_ColumnFont);
this.MyDataGrid.ColumnHeaderStyle = m_ColumnHeaderStyle;
}
Styles in wpf have the ability to update attached values so you can declare styles in xaml once:
<DataGrid >
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="FontFamily" Value="{Binding HeaderFont}"/>
</Style>
</DataGrid.ColumnHeaderStyle>
</DataGrid>
The magic happens in Binding and there are few kinds of it.
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private FontFamily _headerFont;
public FontFamily HeaderFont
{
get
{
return _headerFont;
}
set
{
_headerFont = value;
PropertyChanged(this, new PropertyChangedEventArgs("HeaderFont"));
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
}
It is imperative that PropertyChanged event is fired when property changes.

Expand/collapse groups in treeview by clicking the text

How do I do so that it is possible to expand/collaps groups in the TreeView simply by clicking on the text, instead of clicking the arrow to the left.
You should create style for your Tree Item with next setter:
<Style x:Key="TreeItemStyle"
TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded"
Value="{Binding Path=IsExpanded, Mode=TwoWay}"/>
</Style>
Then add to you group view data class observable property named IsExpanded:
private bool _isExpanded;
public bool IsExpanded
{
get
{
return this._isExpanded;
}
set
{
if (this._isExpanded != value)
{
this._isExpanded = value;
this.OnPropertyChanged("IsExpanded");
}
}
}
Then intercept hyper link click event and set IsExpanded as true:
private void Hyperlink_Click(object sender, RoutedEventArgs e)
{
var dc = ((Hyperlink)sender).DataContext;
if (dc is GroupViewData)
{
((GroupViewData)dc).IsExpanded = true;
}
}
Of course, the best way is to use commands instead of click handlers, but I don't know composition of your presentation model so can't provide proper solution. I just must say that in our projects with alike requirements we successfully avoid any view code behind. God bless WPF!

Resources