Hiding null elements in databound ObserveableCollection - wpf

I have multiple ObservableCollection<T>s that are bound to a TreeView using a HierarchicalDataTemplate similar to the implementation found in How to mix databound and static levels in a TreeView?.
My problem is one (or more) of the elements in the collections can be null on occasion but they are still displayed in the TreeView as a blank line, such as in this example:
Other than removing them from the collection, is there a method to hiding these elements so they will not appear in the User Interface until they are changed to a non-null value?
If the structure of the XAML is relavant, this is roughly what I am doing:
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:FolderNode}"
ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Path=DisplayName}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:ParentItem}">
<HierarchicalDataTemplate.ItemsSource>
<MultiBinding>
<MultiBinding.Converter>
<local:MultiCollectionConverter />
</MultiBinding.Converter>
<Binding Path="Children1" />
<Binding Path="Children2" />
</MultiBinding>
</HierarchicalDataTemplate.ItemsSource>
<TextBlock Text="{Binding Path=DisplayName}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:ChildItemWithChildCollection}"
ItemsSource="{Binding}">
<TextBlock Text="{Binding Path=DisplayName}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:ChildItemWithChild}"
ItemsSource="{Binding Path=GrandChildren}">
<TextBlock Text="{Binding Path=DisplayName}" FontWeight="Bold" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:GrandChildItem}">
<TextBlock x:Name="Item" Text="{Binding Path=DisplayName}" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ChildItemWithoutChildCollection}">
<TextBlock Text="{Binding Path=DisplayName}" />
</DataTemplate>
</TreeView.Resources>

If it's whole collection item that is null you can collapse TreeViewItem when DataContext is null
<TreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>

Related

Change displayed text in XAML Combobox multi selection

I have the following code to allow users to select multiple items from a combobox. However when they click one item, it makes this the displayed text when combobox closes. Can I change the displayed text to something that isnt just the item selected. For example if the users select items A,B and D, I want the text part of the combobox to show "A, B, D"
<ComboBox ItemsSource="{Binding ListOfItems}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}" Width="20" />
<TextBlock Text="{Binding DisplayName}" Width="110" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Thanks
You could use a ContentControl with a Style that changes the ContentTemplate property for the selected item. The following sample markup should give you the idea.
<ComboBox ItemsSource="{Binding ListOfItems}">
<ComboBox.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="ContentTemplate">
<Setter.Value>
<!-- the template for the items in the dropdown list -->
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}" Width="20" />
<TextBlock Text="{Binding DisplayName}" Width="110" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ComboBoxItem}}" Value="{x:Null}">
<Setter Property="ContentTemplate">
<Setter.Value>
<!-- the template for the selected item-->
<DataTemplate>
<ItemsControl ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource AncestorType=ComboBox}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}" Margin="0 0 5 0"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Please refer to the following similar question for more information.
Can I use a different Template for the selected item in a WPF ComboBox than for the items in the dropdown part?

DataTrigger WPF

I have two lists. List of Things and List of Fruits. If an item in the list of things is a fruit from the fruits list, I would like that item to be highlighted.
I would like this with data-binding and thru xmal and not code-behind b/c I am doing MVVM pattern. I have tried it with DataTrigger and Converter but can't get it to work. Please help.
Thanks.
<ListBox ItemsSource="{Binding Things}"
Name="ListOfThigns"
Grid.Row="1">
<DataTemplate>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Fruits}" >
<Setter Property="ListBoxItem.Background" Value="Green"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox>
<ListBox ItemsSource="{Binding Fruits}"
Name="ListOfFruits"
Grid.Column="1"
Grid.Row="1">
</ListBox>
Or, if Fruit inherits from Thing (a fruit is a thing):
<ListView ItemsSource="{Binding Things}">
<ListView.Resources>
<DataTemplate DataType="{x:Type dm:Fruit}">
<TextBlock Text="{Binding Name}" Background="RoyalBlue"/>
</DataTemplate>
<DataTemplate DataType="{x:Type dm:Thing}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListView.Resources>
</ListView>

Conditional formatting for the Header content

I have the following template for some header:
<telerik:RadExpander.Header>
<StackPanel Orientation="Horizontal" DataContext="{Binding CurrentItem, ElementName=activityProductGrid}">
<TextBlock Text="{Binding Strings[Product], Source={StaticResource LanguageResources}, StringFormat='{}{0}: '}" />
<TextBlock Text="{Binding Product.Name}" FontWeight="Bold" />
<TextBlock Text="{Binding Strings[GroupName], Source={StaticResource LanguageResources}, StringFormat=', {0}: '}" />
<TextBlock Text="{Binding GroupName}" />
<TextBlock Text="{Binding Strings[UnitPrice], Source={StaticResource LanguageResources}, StringFormat=', {0}: '}" />
<TextBlock Text="{Binding UnitPrice}" />
</StackPanel>
</telerik:RadExpander.Header>
How can I do conditional formmatting of the header, if for example Product == null, then Header should not display anything?
Edit: This one works.
<StackPanel Orientation="Horizontal" DataContext="{Binding CurrentItem, ElementName=activityProductGrid}">
<i:Interaction.Triggers>
<ie:DataTrigger Binding="{Binding}" Value="{x:Null}">
<ie:ChangePropertyAction TargetObject="{RelativeSource={RelativeSource AncestorType=StackPanel}}" PropertyName="Visibility">
<ie:ChangePropertyAction.Value>
<Visibility>Collapsed</Visibility>
</ie:ChangePropertyAction.Value>
</ie:ChangePropertyAction>
</ie:DataTrigger>
</i:Interaction.Triggers>
<-- from this point the came code as above -->
</StackPanel>
Specifically for null, you can specify binding parameters for a replacement value, or one to display in case of binding errors:
<TextBlock
Text="{Binding Product.Name, TargetNullValue=(empty), FallbackValue=(error)}"/>
(Taken from the WPF Binding Cheatsheet)
EDIT: I noticed that you didn't just want a specific binding to have a different value, but the whole control to not be displayed. For this, you can use Styles and Triggers, binging a DataTrigger to your Product property, and setting Visibility if it's null.
<telerik:RadExpander.Header>
<StackPanel>
<StackPanel.Style>
<Style TargetType="StackPanel">
<Style.Triggers>
<DataTrigger Binding="{Binding Product}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
</StackPanel>
</telerik:RadExpander.Header>

How can I use HierarchicalDataTemplate to display XML Elements and Attributes?

I'd like to display arbitrary XML in a TreeView, with expanding and collapsing nodes, showing both the element name and the set of attributes and their values. I think I can do this with HierarchicalDataTemplate .
I've seen the hints to use HierarchicalDataTemplate to display arbitrary XML elements, and text nodes, like this:
<Window.Resources>
<HierarchicalDataTemplate x:Key="NodeTemplate">
<TextBlock x:Name="tbName" Text="?" />
<HierarchicalDataTemplate.ItemsSource>
<Binding XPath="child::node()" />
</HierarchicalDataTemplate.ItemsSource>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=NodeType}" Value="Text">
<Setter TargetName="tbName" Property="Text" Value="{Binding Path=Value}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=NodeType}" Value="Element">
<Setter TargetName="tbName" Property="Text" Value="{Binding Path=Name}"/>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
<XmlDataProvider x:Key="xmlDataProvider">
</XmlDataProvider>
</Window.Resources>
....
<TreeView Name="treeView1"
ItemsSource="{Binding Source={StaticResource xmlDataProvider}, XPath=*}"
ItemTemplate= "{StaticResource NodeTemplate}"/>
Which works great. It displays the element names and text for each element. But my XML uses attributes to carry information. The schema is complex and I don't have a formal definition of it, so for now I am treating it as arbitrary XML.
The simplest document looks like this:
<c4soap name="GetVersionInfo" seq="" result="1">
<versions>
<version name="Director"
version="2.1.0.126418"
buildtype=""
builddate="Jun 1 2011" buildtime="14:52:43" />
<version name="MediaManager"
version="2.1.0.126418"
buildtype=""
builddate="Jun 1 2011"
buildtime="14:36:17" />
</versions>
</c4soap>
Using the above HierarchicalDataTemplate definition, I get this for a display:
Not quite what I want. For each node I want to display both the element name and the set of attributes and their values.
I tried this:
<Window.Resources>
<HierarchicalDataTemplate x:Key="NodeTemplate">
<WrapPanel
Focusable="False">
<TextBlock x:Name="tbName" Text="?" />
<TextBlock x:Name="tbAttrs" Text="?" />
</WrapPanel>
<HierarchicalDataTemplate.ItemsSource>
<Binding XPath="child::node()" />
</HierarchicalDataTemplate.ItemsSource>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=NodeType}" Value="Text">
<Setter TargetName="tbName" Property="Text" Value="{Binding Path=Value}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=NodeType}" Value="Element">
<Setter TargetName="tbName" Property="Text" Value="{Binding Path=Name}"/>
<Setter TargetName="tbAttrs" Property="Text" Value="{Binding Path=Attributes}"/>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
<XmlDataProvider x:Key="xmlDataProvider">
</XmlDataProvider>
</Window.Resources>
... which gets me kinda close, but the
Value="{Binding Path=Attributes}" results in a display of "(Collection)" in the TreeView.
How can I simply display all the actual attribute names and values, in addition to the element name?
I added an ItemsControl into the template, like this :
<Window.Resources>
<SolidColorBrush x:Key="xmlValueBrush" Color="Blue" />
<SolidColorBrush x:Key="xmAttributeBrush" Color="Red" />
<SolidColorBrush x:Key="xmlTagBrush" Color="DarkMagenta" />
<SolidColorBrush x:Key="xmlMarkBrush" Color="Blue" />
<DataTemplate x:Key="attributeTemplate">
<StackPanel Orientation="Horizontal"
Margin="3,0,0,0"
HorizontalAlignment="Center">
<TextBlock Text="{Binding Path=Name}"
Foreground="{StaticResource xmAttributeBrush}"/>
<TextBlock Text="=""
Foreground="{StaticResource xmlMarkBrush}"/>
<TextBlock Text="{Binding Path=Value}"
Foreground="{StaticResource xmlValueBrush}"/>
<TextBlock Text="""
Foreground="{StaticResource xmlMarkBrush}"/>
</StackPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="nodeTemplate">
<StackPanel Orientation="Horizontal"
Focusable="False">
<TextBlock x:Name="tbName" Text="?" />
<ItemsControl
ItemTemplate="{StaticResource attributeTemplate}"
ItemsSource="{Binding Path=Attributes}"
HorizontalAlignment="Center">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
<HierarchicalDataTemplate.ItemsSource>
<Binding XPath="child::node()" />
</HierarchicalDataTemplate.ItemsSource>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=NodeType}" Value="Text">
<Setter TargetName="tbName" Property="Text" Value="{Binding Path=Value}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=NodeType}" Value="Element">
<Setter TargetName="tbName" Property="Text" Value="{Binding Path=Name}"/>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
<XmlDataProvider x:Key="xmlDataProvider">
</XmlDataProvider>
</Window.Resources>
Now it displays element names and the set of attributes and their values, like this:
you also can use a template selector for the different node types and use the XPath node()|#* to loop thru all types of nodes:
<TreeView
x:Name="TreeView"
ItemsSource="{Binding}"
ItemTemplateSelector="{DynamicResource ResourceKey=NodeTemplateSelector}">
<TreeView.Resources>
<HierarchicalDataTemplate x:Key="TextTemplate">
<Grid>
<uixml:TextInputControl DataContext="{Binding}" />
</Grid>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="AttributeTemplate">
<Grid>
<uixml:AttributeInputControl DataContext="{Binding}" />
</Grid>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="NodeTemplate" >
<TextBlock Text="{Binding Path=Name}" />
<HierarchicalDataTemplate.ItemsSource>
<Binding XPath="child::node()|#*" />
</HierarchicalDataTemplate.ItemsSource>
</HierarchicalDataTemplate>
<ui:XmlTemplateSelector
x:Key="NodeTemplateSelector"
NodeTemplate="{StaticResource NodeTemplate}"
TextTemplate="{StaticResource TextTemplate}"
AttributeTemplate="{StaticResource AttributeTemplate}" />
</TreeView.Resources>
</TreeView>
and :
public class XmlTemplateSelector:DataTemplateSelector{
public DataTemplate NodeTemplate { get; set; }
public DataTemplate TextTemplate { get; set; }
public DataTemplate AttributeTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
XmlNode node = (XmlNode)item;
switch (node.NodeType) {
case XmlNodeType.Attribute:
return AttributeTemplate;
case XmlNodeType.Element:
return NodeTemplate;
case XmlNodeType.Text:
return TextTemplate;
}
throw new NotImplementedException(String.Format("not implemented for type {0}", node.NodeType));
}
}

TreeView with buttons

I have a TreeView where each node has an icon and a descriptive text. But I do not want any node can be selected. Instead, I want that each node act as a button. It run a command when the user press it. But it may not look like a button or hyperlink
This is what I tried so far. The problem is that the text is blue and the text underlined. In addition, sometime the node is selected and therefore blue.
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type vm:ListGroupViewModel}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Text}" FontWeight="Bold" ></TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type vm:ListNodeViewModel}">
<TextBlock>
<Hyperlink TextDecorations="{x:Null}" Command="{Binding ClickCommand, Mode=OneTime}">
<StackPanel Orientation="Horizontal">
<Image Margin="0,2,2,0" Source="{Binding Icon}" />
<TextBlock Text="{Binding Text}" />
</StackPanel>
</Hyperlink>
</TextBlock>
</DataTemplate>
</TreeView.Resources>
You should override hyperlink style:
<Style x:Key="HyperlinkStyle" TargetType="Hyperlink">
<Setter Property="Foreground"
Value="Black"/>
<Setter Property="TextDecorations"
Value="{x:Null}"/>
</Style>
<DataTemplate DataType="{x:Type vm:ListNodeViewModel}">
<TextBlock>
<Hyperlink Command="{Binding ClickCommand, Mode=OneTime}"
Style="{StaticResource HyperlinkStyle}">
<StackPanel Orientation="Horizontal">
<Image Margin="0,2,2,0" Source="{Binding Icon}" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</Hyperlink>
</TextBlock>
</DataTemplate>
To hide tree item selection you could override SystemColors.HighlightBrushKey, which tree view uses to highlight items:
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent"/>

Resources