Visibility Converter Binding Not Working - c#

I have a visibility converter for a a DataGrid that should hide the grid when the item source for the grid is null. The item source is a property of the class for the window.
Here is partial XAML for the window - the window and visibility converter definition and the data grid:
Window:
<Window x:Name="DiagramWindow"
x:Class="FabricAnalyzer.FabricDiagram"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:FabricAnalyzer"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
mc:Ignorable="d"
Title="FabricDiagram"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<ResourceDictionary>
<local:SwitchThumbColorConverter x:Key="SwitchThumbColor"/>
<local:PortThumbColorConverter x:Key="PortThumbColor"/>
<local:StringLengthVisiblityConverter x:Key="VisConverter"/>
<local:PortListVisiblityConverter x:Key="PortVisConverter"/>
Datagrid:
<Grid Name="FabricGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<DataGrid Name="SVCPortDataGrid" Grid.Column="0" Width="Auto"
Visibility="{Binding Path=PortList, Converter=
{StaticResource PortVisConverter}}"
AutoGenerateColumns="False">
Here is the code behind for the property it should bind to and the VisibilityConverter. the idea is that if the PortList is null - it will be by default - the DataGrid should stay collapsed. I have verified that the PortList is null when I want it to be.
public partial class FabricDiagram : Window
{
public List<PortResult> PortList = null;
lastly the visibilityconverter. I have verified in the debugger that it is not getting called.
public class PortListVisiblityConverter : IValueConverter
{
public Object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null )
{
return Visibility.Collapsed;
}
else
{
return Visibility.Visible;
}
}
public Object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I've tried changing the XAML to this binding
Visibility="{Binding PortList, Converter={StaticResource PortVisConverter}}"

Your binding is failing, so the converter never runs.
public List<PortResult> PortList = null;
declares a field and you can only bind to properties. Changing to:
public List<PortResult> PortList { get; set; } = null;
will solve your first problem; then you need to use INotifyPropertyChanged if you want changes to that property to propagate to the UI.
As an aside, you could have figured this out if you looked at the output window while running and saw System.Data exceptions. Easiest way to debug binding issues :)

Related

WPF Converter not running when binding to the whole object and some properties has changed

I've reached my mental barier and not able to figure it out. I'm sure I'm missing something simple but I'm stuck. The code below is the minimal code required to see my problem but is far away from my production code.
Setting:
I have a WPF window with a DataGrid control that is bound to a business object that includes a collection of assets. For every asset I need to display a user control (SpecialButton) which visibility is determined based on multiple properties of an asset object. When I click on the button (in my example I have an extra button that changes the properties for simplicity) it changes a property of the underlying asset object which should make the control hidden.
Problem
I bind the user control attached property ControlVisibility to the whole asset object {Binding .}
<local:SpecialButton x:Name="buttonOnEachRow" ControlId="{Binding Id}"
ControlVisibility="{Binding ., Converter={StaticResource MyConverter}}"/>
When I change a property of the Asset object PropertyAI expect the MyConverter should run and change the visibility value but it never happen.
What I've tried
I've tried so many things that I even don't remember. The most promising seems to be MultipleBinding but I was not able to figure out how to write the syntax for the ControlVisibility property. I tried some settings on the DataGrid control, changing the way how the user control is updated but no vail.
As a workaround, in my production code, I created a fake property that performs the logic that is currently in the converter and bind the ControlVisibility to the fake property. That works but I have a completely unrelated property in my asset object that is there just because I can't figure out the binding.
The main WPF Window
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
namespace MultiBindingProblem
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
var sut = new BusinessObject() { Caption = "This is the parent object", Assets = new List<Asset>()};
sut.Assets.Add(new Asset() { Name = "Asset 1", Id = 1 });
sut.Assets.Add(new Asset() { Name = "Asset 2", Id = 2 });
sut.Assets.Add(new Asset() { Name = "Asset 3", Id = 3 });
this.DataContext = sut;
}
public event PropertyChangedEventHandler PropertyChanged;
private void BtnCancel_Click(object sender, RoutedEventArgs e)
{
Close();
}
private void BtnChange_Click(object sender, RoutedEventArgs e)
{
((BusinessObject)this.DataContext).Assets[0].PropertyA = true;
//PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Assets"));
}
}
}
XAML
The btnChange is here for simplicity. In my production code the SpecialButton will trigger the property update in my viewmodel
<Window x:Class="MultiBindingProblem.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MultiBindingProblem"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" WindowStartupLocation="CenterScreen">
<Window.Resources>
<local:TestConverter x:Key="MyConverter" />
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*" />
<ColumnDefinition Width="0.5*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<TextBlock x:Name="lblMainObject" Grid.ColumnSpan="2" Grid.Row="0" FontSize="25"
Text="{Binding Caption}" />
<Button x:Name="btnCancel" Content="Cancel" IsCancel="True" IsDefault="True" Grid.Row="2" Grid.Column="1" Click="BtnCancel_Click" />
<DataGrid x:Name="dgrData" Grid.Row="1" Grid.ColumnSpan="2" AutoGenerateColumns="False" CanUserAddRows="False"
ItemsSource="{Binding Assets, NotifyOnSourceUpdated=True}" >
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridTemplateColumn Header="Action button" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<!--
Here I bind to the whole 'Asset' object to be able to determine if the button should be
visible based on multiple properties. But changing a propety doesn't raise the converter.
I tried use multiple bindings but I was not able to figure out the syntax
-->
<local:SpecialButton x:Name="buttonOnEachRow"
ControlId="{Binding Id}"
ControlVisibility="{Binding ., Converter={StaticResource MyConverter}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Button x:Name="btnChange" Grid.Row="2" Grid.Column="0" Content="Change visibility of the first button" Click="BtnChange_Click" />
</Grid>
</Window>
The user control (SpecialButton)
using System.Windows;
using System.Windows.Controls;
namespace MultiBindingProblem
{
public partial class SpecialButton : UserControl
{
public SpecialButton()
{
InitializeComponent();
}
public static readonly DependencyProperty ControlIdProperty =
DependencyProperty.Register("ControlId", typeof(int),
typeof(SpecialButton));
public int ControlId
{
get { return (int)GetValue(ControlIdProperty); }
set { SetValue(ControlIdProperty, value); }
}
public static readonly DependencyProperty ControlVisibilityProperty =
DependencyProperty.Register("ControlVisibility", typeof(Visibility),
typeof(SpecialButton), new FrameworkPropertyMetadata(Visibility.Visible));
public Visibility ControlVisibility
{
get { return (Visibility)GetValue(ControlVisibilityProperty); }
set { SetValue(ControlVisibilityProperty, value); }
}
private void btnSpecialButton_Click(object sender, RoutedEventArgs e)
{
System.Windows.MessageBox.Show($"The id of the button: {((Button)sender).Tag.ToString()}");
}
}
}
XAML
<UserControl x:Class="MultiBindingProblem.SpecialButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MultiBindingProblem"
mc:Ignorable="d"
d:DesignHeight="45" d:DesignWidth="80"
x:Name="parent">
<Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch" DataContext="{Binding ElementName=parent}">
<Button x:Name="btnSpecialButton" Content="Click Me" Click="btnSpecialButton_Click"
Tag="{Binding ControlId}"
Visibility="{Binding ControlVisibility}" />
</StackPanel>
</Grid>
</UserControl>
TestConverter
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace MultiBindingProblem
{
public class TestConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var asset = value as Asset;
if (asset == null) return Visibility.Hidden;
return !(asset.PropertyA || asset.PropertyB) ? Visibility.Visible : Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Question
Can I somehow use Multibinding?
Or
How to make the converter run when a single property has changed on the asset object?
You would use a MultiBinding with a multi-value converter like this
<Window.Resources>
<local:MultiBooleanToVisibilityConverter x:Key="MyConverter"/>
</Window.Resources>
<local:SpecialButton ...>
<local:SpecialButton.ControlVisibility>
<MultiBinding Converter="{StaticResource MyConverter}">
<Binding Path="PropertyA"/>
<Binding Path="PropertyB"/>
</MultiBinding>
</local:SpecialButton.ControlVisibility>
</local:SpecialButton>
Your current converter implementation looks like it should return Visible if none of the input properties is true. An equivalent multi-value converter could be this:
public class MultiBooleanToVisibilityConverter : IMultiValueConverter
{
public object Convert(
object[] values, Type targetType, object parameter, CultureInfo culture)
{
bool any = values.Any(v => v is bool && (bool)v);
return any ? Visibility.Hidden : Visibility.Visible;
}
public object[] ConvertBack(
object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}

Conducting MahApps.Metro HamburgerMenu with Caliburn Micro

I am having a few issues with using Caliburn Micro's Conductor<>.Collection.OneActive with MahApps.Metro HamburgerMenu. From a few samples, but none of them address my scenario.
All of my code is available in this Github repository.
I want to show a set of panes inside a HamburgerMenu. Each pane has a title and a display name:
public interface IPane : IHaveDisplayName, IActivate, IDeactivate
{
PackIconModernKind Icon { get; }
}
In my case, IPane is implemented using PaneViewModel:
public class PaneViewModel : Screen, IPane
{
public PaneViewModel(string displayName, PackIconModernKind icon)
{
this.Icon = icon;
this.DisplayName = displayName;
}
public PackIconModernKind Icon { get; }
}
This has the following view:
<UserControl x:Class="CaliburnMetroHamburgerMenu.Views.PaneView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Padding="12"
Background="Pink">
<StackPanel Orientation="Vertical">
<TextBlock Text="Non-bound text" />
<TextBlock x:Name="DisplayName" FontWeight="Bold" />
</StackPanel>
</UserControl>
My shell view model is also quite simple. It inherits from Conductor<IPane>.Collection.OneActive, and takes in a list of panes that it adds to its Items collection:
public class ShellViewModel : Conductor<IPane>.Collection.OneActive
{
public ShellViewModel(IEnumerable<IPane> pages)
{
this.DisplayName = "Shell!";
this.Items.AddRange(pages);
}
}
Now, this is very it gets fuzzy for me. This is an excerpt from ShellView.xaml:
<controls:HamburgerMenu
ItemsSource="{Binding Items, Converter={StaticResource PaneListToHamburgerMenuItemCollection}}"
SelectedItem="{Binding ActiveItem, Mode=TwoWay, Converter={StaticResource HamburgerMenuItemToPane}}">
<ContentControl cal:View.Model="{Binding ActiveItem}" />
<controls:HamburgerMenu.ItemTemplate>
<DataTemplate>
<Grid x:Name="RootGrid"
Height="48"
Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="48" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<iconPacks:PackIconModern
Grid.Column="0"
Kind="{Binding Icon}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="White" />
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
FontSize="16"
Foreground="White"
Text="{Binding Label}" />
</Grid>
</DataTemplate>
</controls:HamburgerMenu.ItemTemplate>
</controls:HamburgerMenu>
To make this work, I rely on two converters (who quite frankly do more than they should have to). One converter takes a ICollection<IPane> and creates a HamburgerMenuItemCollection with HamburgerMenuIconItems that are now contain a two-way link using the Tag properties of both the view model and the menu item.
class PaneListToHamburgerMenuItemCollection : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var viewModels = value as ICollection<IPane>;
var collection = new HamburgerMenuItemCollection();
foreach (var vm in viewModels)
{
var item = new HamburgerMenuIconItem();
item.Label = vm.DisplayName;
item.Icon = vm.Icon;
item.Tag = vm;
vm.Tag = item;
collection.Add(item);
}
return collection;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The second converter converts between the view model and the menu item using this Tag whenever the SelectedItem changes:
class HamburgerMenuItemToPane : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((IPane)value)?.Tag;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((HamburgerMenuIconItem)value)?.Tag;
}
}
When I run this code, and click the items in the hamburger menu, the page switches every time. The issue is that when the app first runs, there is no selected pane, and you cannot set one using any of the activation overrides available in ShellViewModel (such as OnViewAttached or OnActivate, or event the constructor), as the converter code that hooks up the Tag hasn't run yet.
My requirements for a working solution:
Caliburn's conductor must be in charge, as there are views and view models further down the stack that depend on the activation logic to run.
It should be possible to activate the first item from Caliburn at some point during the activation of ShellViewModel
Should respect separation of concerns, i.e. the view model should not know that a hamburger menu is being used in the view.
Please see the GitHub repository for a solution that should run straight away.
I believe the issue is caused by the HamburgerMenu_Loaded method inside the control. If there is a selected item before the control loads, the content of the hamburger menu is replaced:
private void HamburgerMenu_Loaded(object sender, RoutedEventArgs e)
{
var selectedItem = this._buttonsListView?.SelectedItem ?? this._optionsListView?.SelectedItem;
if (selectedItem != null)
{
this.SetCurrentValue(ContentProperty, selectedItem);
}
}
In your case, the ContentControl is removed and your Conductor cannot do its job.
I'm trying to see if this behavior can be changed in MahApps directly, by changing the code to something like this:
if (this.Content != null)
{
var selectedItem = this._buttonsListView?.SelectedItem ?? this._optionsListView?.SelectedItem;
if (selectedItem != null)
{
this.SetCurrentValue(ContentProperty, selectedItem);
}
}

WPF - Detect design mode in a Converter

I have a converter in which I want to be able to change the value to Visibility.Collapsed when in DesignMode. Right it is ignoring the GetIsInDesignMode.
Also, I am binding the VM via dependency injectio (prism)
Converter:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (DesignerProperties.GetIsInDesignMode(new DependencyObject()))
return Visibility.Collapsed;
if (value != null && value is AllowedSourceCode)
{
var allowedSourceCode = (AllowedSourceCode)value;
if (value == null)
return Visibility.Visible;
else if (allowedSourceCode.SupportedSourceCodes.Contains(allowedSourceCode.SelectedSourceCode))
{
return Visibility.Collapsed;
}
else
return Visibility.Visible;
}
return Visibility.Collapsed;
}
View:
<Canvas Visibility="{Binding SupportedSourceCodes,Converter={StaticResource AllowedSourcesConverter}}" Background="Gray" Opacity="0.9"
Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Panel.ZIndex="5" >
xaml.cs:
public ACARSubLedgerUC(ACARSubLedgerVM vm)
{
InitializeComponent();
DataContext = vm;
}
What you're doing should work.
I'm guessing you have a viewmodel behind your window and using the converter on a binding to that viewmodel. Please make sure you are setting your data context in XAML and not in code, because if you are setting it in code your converter will never hit in design mode.
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="525"
Height="350"
mc:Ignorable="d">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
...
</Window>
This ensures bindings are updated at design time and hence your converter will be called.
I wrote a markup extension that makes it way easier to muck around with design time bindings and property values without messing around with the code your view binds to or changing the actual runtime value of a property. The full write up is here: http://www.singulink.com/CodeIndex/post/wpf-visibility-binding-with-design-time-control
It works with visibility as well as most other properties. Usage looks like:
<Grid Visibility="{data:Value {Binding RootObject, Converter={StaticResource NullToVisibilityConverter}}, DesignValue=Visible}">
<TextBlock Background="Red" Text="Testing visibility" />
</Grid>
The extension class:
public class ValueExtension : MarkupExtension
{
public object DesignValue { get; set; } = DependencyProperty.UnsetValue;
[ConstructorArgument("value")]
public object Value { get; set; } = DependencyProperty.UnsetValue;
public ValueExtension() { }
public ValueExtension(object value)
{
Value = value;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var provideValueTarget = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
var target = provideValueTarget.TargetObject as FrameworkElement;
var value = DesignerProperties.GetIsInDesignMode(target) && DesignValue != DependencyProperty.UnsetValue ? DesignValue : Value;
if (value == DependencyProperty.UnsetValue || value == null)
return value;
if (value is MarkupExtension)
{
return ((MarkupExtension)value).ProvideValue(serviceProvider);
}
var property = provideValueTarget.TargetProperty as DependencyProperty;
if (property.PropertyType.IsInstanceOfType(value))
return value;
return TypeDescriptor.GetConverter(property.PropertyType).ConvertFrom(value);
}
}

XAML, C# : How to Set ListView Visibility to Collapse/Visible on Checkbox value toggle?

i am newbie in C# and Windows app development, just for learning purpose i am trying to build a Windows 10 universal app. I am experimenting with Hub view.
Below is the Xaml structure of my file.
<Hub>
<HubSection1>
//SomeData here
</HubSection1>
<HubSection2>
<DataTemplate>
<Grid>
<ListView1>
<CheckBox1>
<ListView2>
//SomeData here
<CheckBox2>
<ListView3>
//SomeData here
<CheckBox3>
<ListView4>
//SomeData here
</ListView1>
</Grid>
</DataTemplate>
</HubSection2>
<HubSection3>
//SomeData here
</HubSection3>
<HubSection4>
//SomeData here
</HubSection4>
</Hub>
So what i am trying to do is to toggle the visibility of ListView(2,3,4) Using Checkboxes(1,2,3) respectively. But in my c# sharp code i am unable to access the variables defined in my XAML file, i tried FindName() in checkbox listeners Method but it didn't helped. is there any way i can fetch data or variables or bind them ??
Use converter concept:
public class BooleanToVisibility : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool isChecked = false;
if (bool.TryParse(value.ToString(), out isChecked))
{
return isChecked ? Visibility.Visible : Visibility.Collapsed;
}
return visibility;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
XAML:
<Window x:Class="MyApp.Windows.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:MyApp.Converters">
<StackPanel>
<StackPanel.Resources>
<converters:BooleanToVisibility x:Key="boolToVisibility"/>
</StackPanel.Resources>
<CheckBox Content="Check to see ListView" Name="changeVisibility"/>
<ListView Visibility="{Binding Path=IsChecked, ElementName=changeVisibility, Converter={StaticResource boolToVisibility}}"/>
</StackPanel>
</Window>

Specify a default empty DataTemplate instead of the default 'ToString()' DataTemplate

The default DataTemplate in a wpf application displays the result of the .ToString() method. I'm developing an application where the default DataTemplate should display nothing.
I've tried:
<Grid.Resources>
<DataTemplate DataType="{x:Type System:Object}">
<Grid></Grid>
</DataTemplate>
</Grid.Resources>
But this doesn't work. Does anyone knows if this is possible without specifiing a specific DataTemplate for every class type in the application?
If you are using the MVVM pattern and have an abstract class which all your ViewModel classes derive from, you can use that class instead of System.Object:
<Grid.Resources>
<DataTemplate DataType="{x:Type vm:VMBase}">
</DataTemplate>
</Grid.Resources>
I know of no way to do this. As per Joe's comment below, WPF specifically disallows specifying a DataTemplate for type Object.
Depending on your exact requirements, it may be easier to search for a DataTemplate that matches the specific type. If you find one, use it. Otherwise, display nothing. For example:
<ContentControl Content="{Binding YourContent}" ContentTemplateSelector="{StaticResource MyContentTemplateSelector}"/>
And in your selector (pseudo-code, obviously):
var dataTemplateKey = new DataTemplateKey() { DataType = theType; };
var dataTemplate = yourControl.FindResource(dataTemplateKey);
if (dataTemplate != null)
{
return dataTemplate;
}
return NulloDataTemplate;
I used Nullable, worked for my situation.
<DataTemplate DataType="{x:Type sys:Nullable}">
<!-- Content -->
</DataTemplate>
I'm not sure about replacing the default DataTemplate, but you can use a ValueConverter to pass display ToString in the case of certain types and an empty string otherwise. Here's some code (note that the typeb textblock doesnt have the converter on it to show what it looks like normally):
.xaml:
<Window x:Class="EmptyTemplate.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:EmptyTemplate"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<loc:AType x:Key="atype"/>
<loc:BType x:Key="btype"/>
<loc:TypeConverter x:Key="TypeConverter"/>
</Window.Resources>
<StackPanel>
<Button Content="{Binding Source={StaticResource atype}, Converter={StaticResource TypeConverter}}"/>
<Button Content="{Binding Source={StaticResource btype}, Converter={StaticResource TypeConverter}}"/>
<TextBlock Text="{Binding Source={StaticResource atype}, Converter={StaticResource TypeConverter}}"/>
<TextBlock Text="{Binding Source={StaticResource btype}}"/>
</StackPanel>
</Window>
.xaml.cs:
namespace EmptyTemplate
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
}
public class AType { }
public class BType { }
public class TypeConverter : IValueConverter
{
public DataTemplate DefaultTemplate { get; set; }
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value.GetType() == typeof(AType))
{
return value.ToString();
}
return DefaultTemplate;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
}
Here a working example about how to do this using a selector (the best way IMO):
public class EmptyDefaultDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item != null)
{
var dataTemplateKey = new DataTemplateKey(item.GetType());
var dataTemplate = ((FrameworkElement) container).TryFindResource(dataTemplateKey);
if (dataTemplate != null)
return (DataTemplate) dataTemplate;
}
return new DataTemplate(); //null does not work
}
}
I discovered something accidentally. I was using a custom dependency property to set the Datacontext on a usercontrol that had a contentcontrol with Datatemplates based on types(entities in my case). Since I had several different kinds of entities my custom dependency property was
` typeof(object)
This was the device I used to bind to the datacontext of the ContentControl.
public object MySelectedItem
{
get { return (object)GetValue(Property1Property); }
set { SetValue(Property1Property, value); }
}
public static readonly DependencyProperty Property1Property
= DependencyProperty.Register(
"MySelectedItem",
typeof(object),
typeof(PromotionsMenu),
new PropertyMetadata(false)
);
Used like this:
MySelectedItem = SomeEntity;
I discovered I could also use it like this:
MySelectedItem = "some text";
And the contextcontrol would print some text as its context.
MySelectedItem = "";
works for a totally blank context.
`

Categories