After Treeview Loaded Event in WPF - c#

I have a TreeView in my WPF application. In runtime I am binding data to the Treeview. Each node in a treeview is associated with path. I should change the color of the TreeView element If the file in the path associated with the element has an error. Say I need to change it to RED.
So Since I am binding it on fly after the treeview is entirely loaded I should traverse the tree again and I should check the path contains any error for every element in the TreeView.
How can I navigate the entire Tree element one by one from parent to root child and perform the error checking operation for each node.
Tree in XAML:
<TreeView Grid.Column="0" Grid.Row="0" HorizontalAlignment="Stretch" Name="treeView1"
VerticalAlignment="Stretch"
SelectedItemChanged="treeView1_SelectedItemChanged" HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Top" BorderThickness="0,0,0,1" BorderBrush="LightGray">
<TreeViewItem Header="Head Tree" ItemsSource="{Binding MainComps}">
<TreeViewItem.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="Foreground" Value="RED" />
</DataTrigger>
</Style.Triggers>
</Style>
</TreeViewItem.ItemContainerStyle>
<TreeViewItem.Resources>
<HierarchicalDataTemplate DataType="{x:Type TextBlock}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Head Tree" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:MainCompViewModel}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Maincompname}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:FeatureViewModel}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FeatureName}" />
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:CompViewModel}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Component}" />
</StackPanel>
</DataTemplate>
</TreeViewItem.Resources>
</TreeViewItem>
</TreeView>
In code behind:
I have treeview selection changed event.
How can I have AfterTreeViewLoaded event. Something like this.
Void TreeviewLoaded()
{
//Identify error. Change the color
}
Thanks in advance..

When you create tree items you should have some list of root items and bind the list to your TreeView.ItemsSource.
Then at any time you can check the paths and set boolean HasError (the property should exist in your ItemViewModel). Tree item's backround can be changed by style, like this:
<Setter Property="Background" Value="{Binding HasError, Converter={StaticResource HasErrorToBackroundConverter}" />
UPD:
Please see the following article http://blog.clauskonrad.net/2011/04/how-to-make-hierarchical-treeview.html.
Your ItemViewModel is just like the Folder from the article.
The list of root items is m_folders. Like the Folder contains a FullPath, your ItemViewModel will contain Path alongside HasError property.
When you need to check the path, you recursively pass m_folders, read the path from it, check it and set HasError.

Related

Dynamic Path in Treeview TextBlock

I am trying to change the Path in WPF Treeview TextBlock dynamically via user selection i.e. dropdown. Upon user interaction the path should take predefined values i.e. Name, Type, Order.
<TreeView x:Name="Main" ItemsSource="{Binding Items, NotifyOnSourceUpdated=True}" >
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type models:Root}"
ItemsSource="{Binding Path=Children}">
<TextBlock Text="{Binding Path=Name}" /> <--- Dynamically change this
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
In C# there is an BindingExpression helper class however I am not clear how to use it in ViewModel scenario
If you want to change the actual binding path, you need to do this programmatically. You cannot dynamically change the binding path in XAML.
A better option would be to change the value of the source property based on the selection in the ComboBox, or use a Style with triggers. Something like this:
<HierarchicalDataTemplate DataType="{x:Type models:Root}"
ItemsSource="{Binding Path=Children}">
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedValue}" Value="Name">
<Setter Property="Content">
<Setter.Value>
<TextBlock Text="{Binding Name}" />
</Setter.Value>
</Setter>
</DataTrigger>
...
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</HierarchicalDataTemplate>

WPF unable to select enabled item within ComboBox

I'm trying to create a ComboBox with some items within the selection list enabled and others disabled. Visually, I'm able to do this but when I select an item that is visually enabled, nothing happens.
XAML:
<ComboBox ItemsSource="{Binding EndpointModel.DisplayFormat}"
VerticalAlignment="Center" Margin="0,0,10,0"
SelectedItem="{Binding EndpointModel.SelectMediaFormat}">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}" />
</Style>
</ComboBox.ItemContainerStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<ComboBoxItem>
<TextBlock Text="{Binding Name}" />
</ComboBoxItem>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
When I click on the selected item above, nothing happens - EndpointModel.SelectMediaFormat is not executed.
The moment I remove the ComboBox.ItemTemplate stanza, I can successfully select any enabled item, i.e.
<ComboBox ItemsSource="{Binding EndpointModel.DisplayFormat}"
VerticalAlignment="Center" Margin="0,0,10,0"
SelectedItem="{Binding EndpointModel.SelectMediaFormat}">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}" />
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
As you can see, i'm displaying the object reference. I can't figure out how to replace the above with the actual Names.
I'm quite new at WPF, as you can imagine :-)
Any suggestions would be appreciated.
Try using the DisplayMemberPath property to Name (The property that you want to display).
<ComboBox ItemsSource="{Binding EndpointModel.DisplayFormat}"
VerticalAlignment="Center" Margin="0,0,10,0"
SelectedItem="{Binding EndpointModel.SelectMediaFormat}"
DisplayMemberPath="Name">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}" />
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>

Command binding to top-level MenuItem of Menu doesn't work

I'm learning WPF and developing a dynamic Menu which is driven by data binding of it's ItemsSource to an ObservableCollection. To do this I have a simple MenuItemViewModel and a HierarchicalDataTemplate for automatic binding of MenuItems to it.
The problem I have is that Command property doesn't work for top level menu items. Despite it is set, a MenuItem doesn't react on mouse click and doesn't get disabled if Command cannot be executed. Simply it's like it's not getting bound.
For lower level menu items though, it works as intended. I think this should be a problem of my HierarchicalDataTemplate but I can't find it, because as I see there's no code in template which might affect command binding of only top-level MenuItems.
MenuItemViewModel implements INotifyPropertyChanged and contains following public properties:
string Text
Uri ImageSource
ICommand Command
ObservableCollection<MenuItemViewModel> Children
HierarchicalDataTemplate for MenuItem in my Window.Resources is as follows:
<HierarchicalDataTemplate DataType="{x:Type common:MenuItemViewModel}"
ItemsSource="{Binding Path=Children}">
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command"
Value="{Binding Command}" />
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ImageSource}" />
<TextBlock Text="{Binding Text}" VerticalAlignment="Center"/>
</StackPanel>
</HierarchicalDataTemplate>
Can you please point me on my mistake?
EDIT: Top-level MenuItem doesn't contain any children (i.e. Children collection of associated ViewModel is empty).
Thanks to #sTrenat comment I've come to solution below.
<Menu.Resources>
<!-- cancel sharing of image so Icon will work properly -->
<Image x:Key="MenuIcon" Source="{Binding ImageSource}" x:Shared="False"/>
</Menu.Resources>
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}"
BasedOn="{StaticResource {x:Type MenuItem}}">
<Setter Property="Header" Value="{Binding Text}" />
<Setter Property="Icon" Value="{StaticResource MenuIcon}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="ItemsSource" Value="{Binding Children}" />
<!-- centering MenuItem's Header -->
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding}" />
</DataTemplate>
</Setter.Value>
</Setter>
<!-- setting Icon to null when ImageSource isn't specified -->
<Style.Triggers>
<DataTrigger Binding="{Binding ImageSource}"
Value="{x:Null}">
<Setter Property="Icon" Value="{x:Null}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Menu.ItemContainerStyle>

Dynamically use different UserControl based on datacontext

I have a chunk of xaml that duplicates the same pattern six times and would like to reduce that footprint by eliminating the duplication. I'm running into one snag that I need help with.
Background: I have a class that instantiates another class six times (phases of a project as you will).
public ECN(string ecnNumber) {
_ECNNumber = ecnNumber;
//instantiate each phase to be populated or not
_PurchaseParts = new ECNPhase();
_PieceParts = new ECNPhase();
_Weldments = new ECNPhase();
_BOMCfg = new ECNPhase();
_Cleanup = new ECNPhase();
_PRAF = new ECNPhase();
}
Inside each phase is a collection of Changes (another class) referenced in the ECNPhase Class. Each phase has data that is unique to each phase that is shown in a unique view, this is where my snag is which I will show later.
Example of the duplicate xaml Code with the main difference being the different view inside each expander:
<StackPanel Margin="0">
<!--Section for Purchase parts-->
<StackPanel Orientation="Horizontal" >
<CheckBox Margin="0,5,5,5" IsChecked="{Binding Path=MyWorkspace.CurrentSelectedItem.PurchaseParts.HasPhase,Mode=TwoWay}"/>
<StackPanel Orientation="Horizontal">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=MyWorkspace.CurrentSelectedItem.PurchaseParts.HasPhase}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Expander Header="Purchase Parts" Margin="0,0,10,0" Width="110">
<view:PurchasePartsView/>
</Expander>
<CheckBox Content="Submit" Margin="10,5,0,5"/> <!--add a command to handle the submit checkbox event-->
<Label Content="Status:" Margin="10,0,0,0" HorizontalContentAlignment="Right" Width="60"/>
<Label Content="{Binding Path=MyWorkspace.CurrentSelectedItem.PurchaseParts.Status}"/>
</StackPanel>
</StackPanel>
<!--Section for Piece Parts-->
<StackPanel Orientation="Horizontal">
<CheckBox Margin="0,5,5,5" IsChecked="{Binding Path=MyWorkspace.CurrentSelectedItem.PieceParts.HasPhase,Mode=TwoWay}"/>
<StackPanel Orientation="Horizontal">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=MyWorkspace.CurrentSelectedItem.PieceParts.HasPhase}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Expander Header="Piece Parts" Margin="0,0,10,0" Width="110">
<view:PiecePartsView/>
</Expander>
<CheckBox Content="Submit" Margin="10,5,0,5"/> <!--add a command to handle the submit checkbox event-->
<Label Content="Status:" Margin="10,0,0,0" HorizontalContentAlignment="Right" Width="60"/>
<Label Content="{Binding Path=MyWorkspace.CurrentSelectedItem.PieceParts.Status}"/>
</StackPanel>
</StackPanel>
<!--duplicated four more times-->
</StackPanel>
What I'd like to do is:
<StackPanel>
<view:PhaseView DataContext="{Binding Path=MyWorkspace.CurrentSelectedItem.PurchaseParts}"/>
<view:PhaseView DataContext="{Binding Path=MyWorkspace.CurrentSelectedItem.PieceParts}"/>
<!--four more phases-->
</StackPanel>
Where the PhaseView will be the template that handles the duplication and this is where I'm hitting a snag. Each phase needs a unique view (userControl) selected based off of the datacontext of the PhaseView.
<StackPanel Orientation="Horizontal" >
<CheckBox Margin="0,5,5,5" IsChecked="{Binding Path=HasPhase,Mode=TwoWay}"/>
<StackPanel Orientation="Horizontal">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=HasPhase}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Expander Header="DisplayName" Margin="0,0,10,0" Width="110">
<!--add somthing here to select the correct view based on the datacontext-->
<!--<local:PurchasePartsView/> This user control adds a datagrid that is unique to each phase-->
</Expander>
<CheckBox Content="Submit" Margin="10,5,0,5"/> <!--add a command to handle the submit checkbox event-->
<Label Content="Status:" Margin="10,0,0,0" HorizontalContentAlignment="Right" Width="60"/>
<Label Content="{Binding Path=Status}"/>
</StackPanel>
</StackPanel>
I was thinking of using a datatrigger somehow lik shown below, but I haven't had any luck figuring it out. I know there's got to be a way to do this, and it's probably something simple and dumb. Any help would be much appreciated.
<DataTrigger Binding="{Binding Path=DisplayName}" Value="Purchase Parts">
<Setter Property="DataContext" Value="{Binding }"/> <!--Don't know how to bind the DataContext-->
</DataTrigger>
Thanks,
Ok, thanks to Bradley I looked into the DataTemplateSelector and this is what I came up with.
In my UserControl resources I set up several DataTemplates and a reference to the TemplateSelector class that overides the DataTemplateSelector class.
XAML Resources:
<UserControl.Resources>
<local:TemplateSelector x:Key="myTemplateSelector"/>
<DataTemplate x:Key="PurchasePartsTemplate">
<view:PurchasePartsView/>
</DataTemplate>
<DataTemplate x:Key="PiecePartsTemplate">
<view:PiecePartsView/>
</DataTemplate>
<!--Four more templates-->
</UserControl.Resources>
Code Behind for DataTemplateSelector override. Note: I couldn't figure out a way to bind to the ECNPhase class so I bound to the DisplayName property in my class to pull out the correct instance being represented.
class TemplateSelector : DataTemplateSelector {
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
FrameworkElement element = container as FrameworkElement;
if(element != null && item != null && item is string) {
string phase = (string)item;
if(phase == "Purchase Parts") {
return element.FindResource("PurchasePartsTemplate") as DataTemplate;
}else if(phase == "Piece Parts") {
return element.FindResource("PiecePartsTemplate") as DataTemplate;
}
}
return null;
}
}
I'm calling this class in my UserContol like this:
<Expander Header="{Binding Path=DisplayName}" Margin="0,0,10,0" Content="{Binding Path=DisplayName}"
ContentTemplateSelector="{StaticResource myTemplateSelector}"/>
There isn't an items control associated with the expander so I used the content control. I pass the DisplayName into the control property and the contentTemplateSelector uses the myTemplateSelector resource which goes into the codebehind and selects the appropriate datatemplate to use based on the DisplayName.
Now I can call my reusable template like so:
<StackPanel Margin="0">
<view:ChangePhaseView DataContext="{Binding Path=MyWorkspace.CurrentSelectedItem.PurchaseParts}"/>
<view:ChangePhaseView DataContext="{Binding Path=MyWorkspace.CurrentSelectedItem.PieceParts}"/>
</StackPanel>
#Bradley, thank you for pointing me in the right direction.

How to show an default text when an item of the TreeView doesn't have subitems

I have an hierarchy like Band - Record, showing on a TreeView. I would like to show in the hierarchy a message like 'No records' when the band doesn't have any records.
I'm trying to use the TargetNullValue, but it isn't working. The band has an ObservableCollection, and if it is null or it has an null value inside it doesn't show the TargetNullValue.
Here is the XAML I'm using:
<TreeView ItemsSource="{Binding Bands, TargetNullValue='No bands'}" >
<TreeView.ItemContainerStyle>
<!--
This Style binds a TreeViewItem to a TreeViewItemViewModel.
-->
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type ViewModels:BandViewModel}"
ItemsSource="{Binding Children, TargetNullValue='No bands'}"
>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding BandName}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate
DataType="{x:Type ViewModels:RecordViewModel}"
ItemsSource="{Binding Children, TargetNullValue='No records'}"
>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding RecordName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Try using the styling solution as per this question

Categories

Resources