Skip to content

Instantly share code, notes, and snippets.

@StevePotter
Last active December 16, 2019 16:56
Show Gist options
  • Save StevePotter/b17f8d4b2657a2d2610390a11fb57e03 to your computer and use it in GitHub Desktop.
Save StevePotter/b17f8d4b2657a2d2610390a11fb57e03 to your computer and use it in GitHub Desktop.
A WPF control that creates its content based on the value of some property in DataContext. This is great when you have a property like "Status" and want to display a particular control for each status.
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Markup;
namespace Wpf
{
/// <summary>
/// Depending on the value of some property of DataContext, this will create the appropriate child view. Views are only created on demand.
/// </summary>
/// <remarks>
/// Todo: use data trigger to activate the proper view, which will also handle type conversion.
/// </remarks>
[ContentProperty("Possibilities")]
public class ConditionalView: Decorator
{
public ConditionalView()
{
this.DataContextChanged += ConditionalView_DataContextChanged;
Possibilities.CollectionChanged += Possibilities_CollectionChanged;
}
private void Possibilities_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
UpdateChild();
}
private void ConditionalView_DataContextChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null)
{
CreateBindingIfNecessary();
}
}
private void CreateBindingIfNecessary()
{
var binding = BindingOperations.GetBinding(this, PropertyValueProperty);
if (binding != null && (binding.Source != DataContext || binding.Path.Path != Property))
{
BindingOperations.ClearBinding(this, PropertyValueProperty);
}
if (DataContext != null && Property.HasChars())
{
binding = new Binding();
binding.Source = DataContext;
binding.Path = new PropertyPath(Property);
binding.Mode = BindingMode.OneWay;
BindingOperations.SetBinding(this, PropertyValueProperty, binding);
}
}
/// <summary>
/// The name of the property on the DataContext to monitor and create the proper view for.
/// </summary>
public string Property
{
get { return (string)GetValue(PropertyProperty); }
set { SetValue(PropertyProperty, value); }
}
// Using a DependencyProperty as the backing store for Property. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PropertyProperty =
DependencyProperty.Register("Property", typeof(string), typeof(ConditionalView), new PropertyMetadata(OnPropertyNameValueChanged));
public static void OnPropertyNameValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (ConditionalView)d;
control.CreateBindingIfNecessary();
control.UpdateChild();
}
protected object PropertyValue
{
get { return (object)GetValue(PropertyValueProperty); }
set { SetValue(PropertyValueProperty, value); }
}
// Using a DependencyProperty as the backing store for PropertyValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PropertyValueProperty =
DependencyProperty.Register("PropertyValue", typeof(object), typeof(ConditionalView), new PropertyMetadata(OnPropertyValueChanged));
public static void OnPropertyValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (ConditionalView)d;
control.UpdateChild();
}
protected void UpdateChild()
{
foreach(var possiblity in Possibilities)
{
if (DoesPossiblityEqualValue(PropertyValue, possiblity.Value))
{
if (possiblity.Template == null)
throw new Exception("Possibility missing its Template");
var o = possiblity.LoadContent() as UIElement;
if (o == null)
throw new Exception("Possibility Template must return UIElement as its root.");
Child = o;
return;
}
}
Child = null;
}
public ObservableCollection<PossibleView> Possibilities
{
get
{
return _Possibilities.Value;
}
}
private Lazy<ObservableCollection<PossibleView>> _Possibilities = new Lazy<ObservableCollection<PossibleView>>();
public static bool DoesPossiblityEqualValue(object value, object possiblity)
{
bool xIsNull = value == null;
bool yIsNull = possiblity == null;
if (xIsNull || yIsNull)
return (xIsNull && yIsNull);
if (Object.Equals(value, possiblity))
{
return true;
}
Type valueType = value.GetType();
Type possiblityType = possiblity.GetType();
try
{
if (valueType.IsEnum)
{
return value.Equals(Enum.Parse(valueType, possiblity.ToString()));//for some reason without the int cast it wouldn't work
}
if (value is int)
{
return Convert.ToInt32(possiblity) == (int)value;
}
if (value is double)
{
return Convert.ToDouble(possiblity) == (double)value;
}
if (value is bool)
{
return Convert.ToBoolean(possiblity) == (bool)value;
}
return string.Equals(value.ToString(), possiblity.ToString(), StringComparison.Ordinal);
}
catch (Exception)
{
return string.Equals(value.ToString(), possiblity.ToString(), StringComparison.Ordinal);
}
}
}
public class PossibleView: ControlTemplate
{
public object Value { get; set; }
}
}
<myWpf:ConditionalView Property="Status">
<myWpf:PossibleView Value="Initializing">
<TextBlock Text="Initializing"></TextBlock>
</myWpf:PossibleView>
<myWpf:PossibleView Value="ReadyToTest">
<TextBlock Text="Ready to go!"></TextBlock>
</myWpf:PossibleView>
</myWpf:ConditionalView>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment