Page indicator using ItemsControl ignores data template - wpf

I'm trying to create a page indicator using ItemsControl. The idea is to bind to the tabs of a TabControl and display a circle for each tab, with the color determined by a trigger that checks whether the tab is selected or not:
<ItemsControl ItemsSource="{Binding Path=Items, ElementName=ATabControl}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type TabItem}">
<Ellipse x:Name="PageIndicator" Width="6" Height="6" Margin="2,0" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected}" Value="False">
<Setter TargetName="PageIndicator" Property="Fill" Value="White" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsSelected}" Value="True">
<Setter TargetName="PageIndicator" Property="Fill" Value="Blue" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
It compiles without error but instead of the circles, I get the names of the tabs listed. Practically, it ignores the ItemTemplate/DataTemplate completely. Actually, if I remove the latter, the display remains the same all right.

There is always an issue while binding UI elements in two different containers. as one UI element can have only 1 parent so the last parent will have the actual element and hence the same will be removed from the former parent containers.
in the issue you've mentioned you attempted to bind the UI element TabItems to the items control which effectively pulled the original elements from the tab control and placed them as child of the items control.
in order to solve this issue I propose a solution to wrap such UI elements in a class and wire the properties needed.
I attempted a solution using converter
xaml
<StackPanel xmlns:l="clr-namespace:CSharpWPF">
<StackPanel.Resources>
<l:TabItemsConverter x:Key="TabItemsConverter" />
</StackPanel.Resources>
<TabControl x:Name="ATabControl">
<TabItem Header="item 1" />
<TabItem Header="item 2" />
<TabItem Header="item 3" />
</TabControl>
<ItemsControl ItemsSource="{Binding Path=Items, ElementName=ATabControl,Converter={StaticResource TabItemsConverter}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse x:Name="PageIndicator"
Width="6"
Height="6"
Margin="2,0"
Fill="Gray" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected}"
Value="False">
<Setter TargetName="PageIndicator"
Property="Fill"
Value="Gray" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsSelected}"
Value="True">
<Setter TargetName="PageIndicator"
Property="Fill"
Value="Blue" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
converter class
namespace CSharpWPF
{
public class TabItemsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
List<TabItemWrapper> result = new List<TabItemWrapper>();
foreach (TabItem item in (ItemCollection)value)
{
result.Add(new TabItemWrapper(item));
}
return result;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
class TabItemWrapper : DependencyObject
{
public bool IsSelected
{
get { return (bool)GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); }
}
// Using a DependencyProperty as the backing store for IsSelected. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsSelectedProperty =
DependencyProperty.Register("IsSelected", typeof(bool), typeof(TabItemWrapper), new PropertyMetadata(false));
public TabItemWrapper(TabItem source)
{
Binding b = new Binding("IsSelected");
b.Source = this;
b.Mode = BindingMode.TwoWay;
source.SetBinding(TabItem.IsSelectedProperty, b);
}
}
}
}
in this converter I binded the tab item's IsSelected to the property of a wrapper class and used it to bind in the view
Note: this converter work for static tab items only, if you intend to add or remove the tab items during runtime then perhaps you may need to handle CollectionChanged events to keep the result in sync.
result

Related

Creating a ListBox style Control with "Show X More"

I would like to create a type of control that will be able to see a narrowed and expanded list by some button, an expander or just a button, and also display some hidden items
I tried to create a style that would have a ListBox of a certain size (let's say it would be enough for 3 items)
And the Expander or some Button that would be below it would say how many more items there are
And give the option to expand and see everything or reduce and return to see 3 items
What I created here works pretty poorly and doesn't look good.
I would appreciate help on how to improve it or how to do it in a better and more correct way
<Style x:Key="CustomListBoxKey" TargetType="{x:Type ListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ItemsPresenter/>
<Expander x:Name="Expander" Grid.Row="1" Header="Show X More" ExpandDirection="Down"/>
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=Expander, Path=IsExpanded}" Value="False">
<Setter Property="MaxHeight" Value="80"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=Expander, Path=IsExpanded}" Value="True">
<Setter Property="MaxHeight" Value="Infinity"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
public List<string> Items { get; set; } = new() { "A", "B", "C", "D", "E", "F", "G", "H", "I" };
<Grid>
<ListBox ItemsSource="{Binding Items}" Style="{StaticResource CustomListBoxKey}" VerticalAlignment="Top">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=.}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
This is an improvement to your code, and it will give you what you asked for
<local:InvertableBooleanToVisibilityConverter x:Key="InvertableBooleanToVisibilityConverterKey"/>
<local:MathValueConverter x:Key="MathValueConverterKey"/>
<Style x:Key="CustomListBoxKey" TargetType="{x:Type ListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ItemsPresenter x:Name="ItemsPresenter"/>
<Expander
x:Name="Expander"
Grid.Row="1"
ExpandDirection="Down">
<Expander.Header>
<StackPanel
Orientation="Horizontal"
Visibility="{Binding RelativeSource={RelativeSource AncestorType=Expander}, Path=IsExpanded, Converter={StaticResource InvertableBooleanToVisibilityConverterKey}}">
<TextBlock Text="Show "/>
<TextBlock
Text="{Binding RelativeSource={RelativeSource Self}, Path=DataContext.Items.Count,
Converter={StaticResource MathValueConverterKey},
ConverterParameter=-2}">
</TextBlock>
<TextBlock Text=" More"/>
</StackPanel>
</Expander.Header>
</Expander>
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=Expander, Path=IsExpanded}" Value="False">
<Setter Property="MaxHeight" Value="80"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=Expander, Path=IsExpanded}" Value="True">
<Setter Property="MaxHeight" Value="Infinity"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
public class MathValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int.TryParse(parameter.ToString(), out var x);
return (int)value + x;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class InvertableBooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is bool and true ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I would like to present a solution using a UserControl. Actually we have a request for two slightly different presentation. As a first step I build a UserControl called DualDisplay. The DualDisplay gets : "Short Display" and "LongDisplay" as parameters.
The DualDisplay UserControl is hosting the Expander:
<UserControl.Resources>
<local:InvertBooleanToVisibilityConverter x:Key="InvertBooleanToVisibilityConverter"/>
<Style TargetType="Expander" x:Key="ExpanderHeaders">
<Style.Triggers>
<Trigger Property="IsExpanded" Value="true">
<Setter Property="Header" Value="Show Less"/>
</Trigger>
<Trigger Property="IsExpanded" Value="false">
<Setter Property="Header" Value="Show More"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<StackPanel Orientation="Vertical">
<Expander Name="expander" IsExpanded="False" Style="{StaticResource ExpanderHeaders}">
<ContentPresenter Content="{Binding ElementName=ours,Path=LongContent}"/>
</Expander>
<ContentPresenter Content="{Binding ElementName=ours,Path=ShortContent}"
Visibility="{Binding Path=IsExpanded,ElementName=expander,Converter={StaticResource ResourceKey=InvertBooleanToVisibilityConverter}}"/>
</StackPanel>
</Grid>
And the using of the DualDisplay will look as following :
<local:DualDisplay>
<local:DualDisplay.ShortContent>
<ListBox ItemsSource="{Binding Items}" VerticalAlignment="Top" MaxHeight="80">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=.}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</local:DualDisplay.ShortContent>
<local:DualDisplay.LongContent>
<ListBox ItemsSource="{Binding Items}" VerticalAlignment="Top" MaxHeight="1000">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=.}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</local:DualDisplay.LongContent>
</local:DualDisplay>
When we use this method we can reuse the DualDisplay for different cases and we are free to design the short/long display as we wish.
I attach also the code behind of the DualDisplay:
public object ShortContent
{
get { return (object)GetValue(ShortContentProperty); }
set { SetValue(ShortContentProperty, value); }
}
public static readonly DependencyProperty ShortContentProperty =
DependencyProperty.Register("ShortContent", typeof(object), typeof(DualDisplay),
new PropertyMetadata(null));
public object LongContent
{
get { return (object)GetValue(LongContentProperty); }
set { SetValue(LongContentProperty, value); }
}
public static readonly DependencyProperty LongContentProperty =
DependencyProperty.Register("LongContent", typeof(object), typeof(DualDisplay),
new PropertyMetadata(null));

Binding a DataTrigger value to this instance of DataTemplate

I have a ListView, with its items represented by an ItemTemplate like so:
<ListView dependencyObjects:InterestingItem.Interesting="{Binding InterestingItem}"
ItemsSource="{Binding Quotations}" >
<ListView.ItemTemplate>
<DataTemplate>
<Border>
<Grid>
<StackPanel x:Name="NotImportant">
</StackPanel>
<Grid x:Name="HiddenGrid"
Background="Red"
Visibility="Hidden" >
<Grid.Style>
<Style TargetType="Grid">
<Style.Triggers>
<Grid.Triggers>
<DataTrigger Binding="{Binding Path=DataContext.InterestingItem,
RelativeSource={RelativeSource AncestorType={x:Type ListView }}}"
Value="*this instance here*!">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Grid.Triggers>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
</Grid>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The ListView has an attached property InterestingItem that is one of the items in the ListView.
What I can't hook up is when the InterestingItem is the same as one of the items, the second Grid should become visible.
I would prefer not to change and bind to the actual objects in the list - but rather have the ListView control which item is to be altered.
What is the Value in the DataTrigger that I need?
There are multiple issues in your XAML and conceptually that prevent it from working.
To bind attached properties, you have to use the correct syntax with parentheses.
Path="{Binding (local:InterestingItem.Interesting), RelativeSource={RelativeSource AncestorType={x:Type ListView}}}"
See the Binding path syntax documenation for reference.
The Triggers property does only support EventTriggers, see FrameworkElement.Triggers.
Note that the collection of triggers established on an element only supports EventTrigger, not property triggers (Trigger). If you require property triggers, you must place these within a style or template and then assign that style or template to the element either directly through the Style property, or indirectly through an implicit style reference.
You cannot bind the Value property of DataTrigger, since it is not a dependency property.
You could of course change the bound type to expose a property that indicates if it is a special object or not and bind that in XAML using a DataTrigger, similar to this (where IsSpecial is the new bool property).
<Grid x:Name="HiddenGrid"
Background="Red">
<TextBlock Text="Hidden Grid"/>
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSpecial}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
If you want to stick to your current approach, you could create a custom IMultiValueConverter that enables binding multiple properties. It would check if all of the bound values are equal and return Visibility.Visible or Visibility.Hidden otherwise. This example uses Linq to check this and supports an arbitrary number of values bound, but there are many other options.
public class EqualityToVisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values is null || values.Length < 2)
return Binding.DoNothing;
return values.Distinct().Count() == 1 ? Visibility.Visible : Visibility.Hidden;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
}
Next, instantiate the converter in the resources of the ListView or any other resource dictionary in scope and bind the Visibility property of the Grid to both the current item (just <Binding/>) and the attached property local:InterestingItem.Interesting with a MultiBinding that uses the converter to convert them to a Visibility.
<ListView local:InterestingItem.Interesting="{Binding InterestingItem}"
ItemsSource="{Binding Quotations}">
<ListView.Resources>
<local:EqualityToVisibilityConverter x:Key="EqualityToVisibilityConverter"/>
</ListView.Resources>
<ListView.ItemTemplate>
<DataTemplate>
<Border>
<Grid>
<StackPanel x:Name="NotImportant">
<TextBlock Text="Not Important"/>
</StackPanel>
<Grid x:Name="HiddenGrid"
Background="Red">
<Grid.Visibility>
<MultiBinding Converter="{StaticResource EqualityToVisibilityConverter}">
<Binding/>
<Binding Path="(local:InterestingItem.Interesting)"
RelativeSource="{RelativeSource AncestorType={x:Type ListView}}"/>
</MultiBinding>
</Grid.Visibility>
<TextBlock Text="Hidden Grid"/>
</Grid>
</Grid>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
There are two other things to note here. I have added two dummy TextBlocks, otherwise the result will not be visible, as the panels are empty. Replace them with your content. Furthermore, both the StackPanel and the Grid are overlapping in the parent Grid, I do not know if this is intentional or not, but you can change it by adding rows or columns and moving the elements there.
What is the Value in the DataTrigger that I need?
I am afraid XAML has no support for something like the this keyword in C#.
You may use a MultiBinding with an IMultiValueConverter implementation that determines whether the items are equal:
<Grid x:Name="HiddenGrid" Background="Red">
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Visibility" Value="Hidden" />
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding>
<MultiBinding.Converter>
<local:MultiConverter />
</MultiBinding.Converter>
<Binding Path="{Binding Path=DataContext.InterestingItem,
RelativeSource={RelativeSource AncestorType={x:Type ListView }}}" />
<Binding Path="{Binding}" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
Converter:
public class MultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) =>
values != null && values.Length == 2 && values[0] == values[1];
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>
throw new NotSupportedException();
}
Note that you cannot set the Visibility property of the Grid to a local value if you want to be able to override the value using a Style setter.
<Grid x:Name="HiddenGrid" Background="Red" Visibility="Hidden">

Add Check Box for Treeview parent nodes only Wpf

I want to add the check box for treeview of observable collection. the following code add the check box for all the parent and children nodes. I need only for the parent node Is there any way to achieve this...
<HierarchicalDataTemplate x:Key="LogFolderExplorer" DataType="{x:Type TestAutomationClient:TestArtifact}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal" MinWidth="200">
<CheckBox
Focusable="False"
IsChecked="{Binding IsChecked}"
VerticalAlignment="Center"
/>
<TextBlock Text="{Binding Name}" FontSize="14"/>
</StackPanel>
</HierarchicalDataTemplate>
There are tons of ways you can achieve this.
One of them:
You can go to the ViewModel for the TestAutomationClient:TestArtifact, and add the bool property here:
public bool IsEmpty { get { return !this.Children.Any() } }
You should update it when the entire collection has changed (if this is your case), i.e. you should make RaisePropertyChanged("IsEmpty"). In case you are using some kind of ICollectionView - you do not need that property on your ViewModel.
<HierarchicalDataTemplate x:Key="LogFolderExplorer"
DataType="{x:Type TestAutomationClient:TestArtifact}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal" MinWidth="200">
<CheckBox Focusable="False"
IsChecked="{Binding IsChecked}"
VerticalAlignment="Center">
<CheckBox.Style>
<Style TargetType="CheckBox">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsEmpty}" Value="True">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
<TextBlock Text="{Binding Name}" FontSize="14"/>
</StackPanel>
</HierarchicalDataTemplate>
You need a DataTrigger with a Converter. The Trigger should look like below,
<HierarchicalDataTemplate ItemsSource="{Binding SubItems}">
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="chk" Visibility="Collapsed"/>
<TextBlock Text="{Binding Name}"
VerticalAlignment="Center"
Margin="3 0" />
</StackPanel>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource TreeLevelFinder}}"
Value="True">
<Setter Property="Visibility"
TargetName="checkBox"
Value="Visible" />
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
The whole TreeViewItem is being send to the Converter. The Converter will look for a Visual Parent. If it finds a Parent as TreeView, it is the first level. If it finds TreeViewItem, then it is sub levels.
public class TreeLevelFinder : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is FrameworkElement)
{
var element = value as FrameworkElement;
var treeItem = element.TemplatedParent as TreeViewItem;
var parent = treeItem.FindAncestor<ItemsControl>();
if (parent is TreeView)
{
return true;
}
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
The extension method FindAncestor<T> is available in this link.

StackPanel Dynamic Binding of Visibility and IsEnabled Properties via Converters

My small objective is to implement a dynamic on-the-go generation of controls and associate their respective properties, such as Visibility and IsEnabled.
The controls must be inserted into the StackPanel. And based on some conditions, the children nodes properties would change. I've used the following nasted StackPanel structure:
The Parent StackPanel with Vertical orientation of Children (collection of Labels ad input Fields to simulate a form)
The Child node StackPanel with Horizontal orientation of Children (Label and an input field)
The main idea is that there is only one input field per each label: Date Picker, Combo, Text or any other. As result, I created a Label and a Grid with multiple Controls. I manipulate Visibility and IsEnabled properties of the input controls via Converters.
Here is the question: is it possible to implement all these by other means (more efficient/aesthetic)? Constructive criticism and suggestions are more than welcome :)
Thank you in advance.
XAML:
<StackPanel Grid.Row="1" Orientation="Vertical">
<ItemsControl ItemsSource="{Binding DataClass}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0, 5, 0, 0" >
<Label Content="{Binding KeyName}" Width="150"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="190"/>
</Grid.ColumnDefinitions>
<DatePicker Text="{Binding Value}"
Visibility="{Binding Type, Converter={StaticResource TypeVisiblity}, ConverterParameter='DateTime'}"
IsEnabled="{Binding RelativeSource={x:Static RelativeSource.Self}, Converter={StaticResource VisibilityEnabled}}"/>
<ComboBox Text="{Binding Value}"
Visibility="{Binding Type, Converter={StaticResource TypeVisiblity}, ConverterParameter='Lookup'}"
IsEnabled="{Binding RelativeSource={x:Static RelativeSource.Self}, Converter={StaticResource VisibilityEnabled}}"/>
<TextBox Text="{Binding Value}"
Visibility="{Binding Type, Converter={StaticResource TypeVisiblity}, ConverterParameter='Number'}"
IsEnabled="{Binding RelativeSource={x:Static RelativeSource.Self}, Converter={StaticResource VisibilityEnabled}}"/>
<TextBox Text="{Binding Value}"
Visibility="{Binding Type, Converter={StaticResource TypeVisiblity}, ConverterParameter='Text'}"
IsEnabled="{Binding RelativeSource={x:Static RelativeSource.Self}, Converter={StaticResource VisibilityEnabled}}"/>
</Grid>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
Visibility Converter:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool isMatched;
string type,
controlType;
// Safe Convert.
type = System.Convert.ToString(value);
controlType = System.Convert.ToString(parameter);
if (string.IsNullOrEmpty(type) || string.IsNullOrEmpty(controlType))
{
return Visibility.Hidden;
}
// Check matching.
isMatched = string.Equals(type, controlType, StringComparison.CurrentCultureIgnoreCase);
return isMatched ? Visibility.Visible : Visibility.Hidden;
}
IsEnabled Converter:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Control control;
control = value as Control;
if (control == null)
{
return false;
}
return control.Visibility == Visibility.Visible;
}
I would use a ContentControl and set the ContentTemplate in a style
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<!-- Default Template -->
<Setter Property="ContentTemplate"
Value="{DynamicResource TextBoxTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Type}" Value="DateTime">
<Setter Property="ContentTemplate"
Value="{DynamicResource DateTimeTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="Lookup">
<Setter Property="ContentTemplate"
Value="{DynamicResource ComboBoxTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Create a user control that has your DatePicker, ComboBox, TextBox etc as user interface and implement all necessary logic in code behind of this user control. You can add dependency properties to implement your logic without converters.
You can add your user control into StackPanel easily by adding it into its Children property.

How to OR a local bool with a viewmodel bool and bind to visibility property

I have the following XAML in a usercontrol that loads a series of gauge usercontrols:
<ItemsControl ItemsSource="{Binding InstanceViper.Gauges}" Grid.Row="7" Grid.ColumnSpan="4">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Name="GaugePanel" VerticalAlignment="Bottom" HorizontalAlignment="Left" Orientation="Horizontal">
</WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:Gauge DataContext="{Binding}" AlwaysShowGauge="False"></local:Gauge>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I am passing in a flag that determines if the gauge should be displayed always or only when in alarm. Now in gauge xaml I want to do something like the following to determine if it should be displayed:
<Border Name="MainBorder" Visibility="{Binding (ShowAlarm || AlwaysShowGauge) ,Converter={StaticResource BooleanToVisibilityConverter}}">
Problem is that the ShowAlarm property is from the gauge viewmodel and AlwaysShowGauge is a local property being passed in in the data template. The ShowAlarm is dynamic but AlwaysShowGauge won't change once set. How can I accomplish this?
Its hard to tell where your border comes from, and where the one of the bindings come from. But you can control the Binding flow, that's not a problem. The main problem is how do you multi trigger, below is just a start. Once you see how you can multi trigger, then, you can play with the binding, either control the flow from one view model or view properties, or play with paths from different sources, to do that is you might have to put the following sample in the DataTemplate and use SourceName and TargetName.. But again, it's hard to tell where the border is.
<converters:OrMultiConverter x:Key="Or" />
<Style x:Key="MainBorderStyle" TargetType="{x:Type Border}">
<Setter Property="Visibility" Value="Hidden" />
<Style.Triggers>
MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Value="True">
<Condition.Binding>
<MultiBinding Converter="{StaticResource Or}">
<Binding Path="ShowAlarm"/>
<Binding Path="AlwaysShowGaug"/>
</MultiBinding>
</Condition.Binding>
</Condition>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Visibility" Value="Visible"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
public class OrMultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Any(value => value == null || value == DependencyProperty.UnsetValue))
return false;
var boolValues = values.Cast<bool>();
var result = boolValues.Any(b => b);
return result;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

Resources