WPF: Parent which would display a specific child depending on input value - c#

As far as I understand, usual Visibility={Binding SomeValue, Converter={…}} still keeps node in visual and logical tree even if it’s invisible. But what if I want to remove it completely and at the same time keep syntax light?
Right now, I’ve made a class called Switch allowing me to do stuff like that:
<Switch Value="{Binding Status}">
<TextBlock Switch.When="{x:Static Status.NotFound}" Text="Not found" />
<Button Switch.When="{x:Static Status.ConnectionError}" Text="Connection error. Try again?" />
<Grid Switch.When="{x:Static Status.Loaded}">…</Grid>
</Switch>
Here is a source code of that Switch thing.
I like how it looks and works, but sometimes some errors occur. For instance, while removing visual child from its previous location, rarely, but System.InvalidOperationException: Cannot modify the logical children for this node at this time because a tree walk is in progress. might happen. Cases like this are solvable, but the whole thing with them makes me think I’m doing something very wrong. What could it be? Maybe the whole idea is just not compatible with WPF at all? Or maybe I’m just missing something (like that thing that I have to override IEnumerator LogicalChildren { get; } to make it work properly)?

I think the answer is probably that you're trying to reparent the child controls the right way. Bad idea! They're just arbitrary content; don't treat them as controls. The actual reparenting in my code is done by hidden ContentPresenter magic in the template. All our control class code does is just sling them around like potatoes.
Here's a working version of the control that shouldn't give you any backtalk. But note that I had trouble comparing boxed enum values to each other. I'm interested in how you solved that problem.
Switch.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
namespace SwitchTestProject
{
[ContentProperty("Items")]
public class Switch : Control
{
public Switch()
{
Items = new List<DependencyObject>();
}
static Switch()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Switch), new FrameworkPropertyMetadata(typeof(Switch)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
OnValueChanged(null);
}
#region Switch.When Attached Property
public static Object GetWhen(DependencyObject obj)
{
return (Object)obj.GetValue(WhenProperty);
}
public static void SetWhen(DependencyObject obj, Object value)
{
obj.SetValue(WhenProperty, value);
}
public static readonly DependencyProperty WhenProperty =
DependencyProperty.RegisterAttached("When", typeof(Object), typeof(Switch),
new PropertyMetadata(null));
#endregion Switch.When Attached Property
#region Content Property
public Object Content
{
get { return (Object)GetValue(ContentProperty); }
protected set { SetValue(ContentPropertyKey, value); }
}
internal static readonly DependencyPropertyKey ContentPropertyKey =
DependencyProperty.RegisterReadOnly(nameof(Content), typeof(Object), typeof(Switch),
new PropertyMetadata(null));
public static readonly DependencyProperty ContentProperty = ContentPropertyKey.DependencyProperty;
#endregion Content Property
#region Value Property
public Object Value
{
get { return (Object)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(nameof(Value), typeof(Object), typeof(Switch),
new FrameworkPropertyMetadata(null, Value_PropertyChanged));
protected static void Value_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as Switch).OnValueChanged(e.OldValue);
}
private void OnValueChanged(object oldValue)
{
if (Value is IComparable)
{
// Boxed value types have to be a special case.
// Unless I jumped to an unwarranted conclusion about == not working.
var icompval = Value as IComparable;
foreach (var item in Items)
{
var icompwhen = GetWhen(item) as IComparable;
if (icompwhen != null && icompval.CompareTo(icompwhen) == 0)
{
Content = item;
return;
}
}
}
else
{
Content = Items.FirstOrDefault(item => GetWhen(item) == Value);
}
}
#endregion Value Property
#region Items Property
public List<DependencyObject> Items
{
get { return (List<DependencyObject>)GetValue(ItemsProperty); }
protected set { SetValue(ItemsPropertyKey, value); }
}
internal static readonly DependencyPropertyKey ItemsPropertyKey =
DependencyProperty.RegisterReadOnly(nameof(Items), typeof(List<DependencyObject>), typeof(Switch),
new PropertyMetadata(null));
public static readonly DependencyProperty ItemsProperty = ItemsPropertyKey.DependencyProperty;
#endregion Items Property
}
}
App.xaml or Themes\Generic.xaml
You could do a lot more with styling the parent here.
<Style TargetType="local:Switch">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:Switch">
<ContentPresenter
/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Usage:
<local:Switch
Value="{Binding Status}"
>
<TextBlock
local:Switch.When="{x:Static local:Status.NotFound}"
>This is a test</TextBlock>
<TextBlock
local:Switch.When="{x:Static local:Status.ConnectionError}"
>There was an error in the connection</TextBlock>
</local:Switch>
Pure XAML alternative
The bug in your Switch control is probably fixable, but this will work reliably without any nonsense (other than all the verbosity).
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="NotFound">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="Not found" />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="ConnectionError">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Button Content="Connection error. Try again?" />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="Loaded">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Ellipse
Height="32"
Width="32"
Fill="DeepSkyBlue"
/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
We set the content using DataTemplates rather than setting the Content property directly because if we use the latter method, only one instance of each child control will ever exist, so we wouldn't be able to factor the style out as a resource and reuse it.
And because templating is the canonical way to create new controls in XAML.

Related

How to change the roundness of a button in a generic way

I am currently working on making my WPF application a little bit more generic.
Up to this point, for each button I wanted to create, I used a different style to modify roundness (and it creates a lot of useless code).
Using the following code I've managed to create a variable I can change from the XAML file, but I cannot link it to the roundness itself.
Could anyone please tell me what I am doing wrong? I already have checked on so many forums but no one seems to have the answer other than "don't do it in a generical way".
I can precise that everything is compiling and the style is otherwise correctly applied to the button (there is no xaml linking problem).
The style I am using:
<Style x:Key="AwakeButton" TargetType="{x:Type customcontrols:AwakeButton}" BasedOn="{StaticResource {x:Type Button}}"
xmlns:extensions="Awake.Services.Properties:Extensions">
<Setter Property="customcontrols:AwakeButton.BorderRoundness" Value="4.0"/>
<Style.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="{Binding Path=BorderRoundness}" />
<!--<Setter Property="CornerRadius" Value="10" />-->
</Style>
</Style.Resources>
</Style>
The overload of the button I created to do so:
public class AwakeButton : Button
{
public AwakeButton()
{
}
public static DependencyProperty BorderRoundnessProperty =
DependencyProperty.RegisterAttached("BorderRoundness", typeof(double), typeof(AwakeButton));
public static void SetBorderRoundness(UIElement element, double value)
{
element.SetValue(BorderRoundnessProperty, value);
}
public static double GetBorderRoundness(UIElement element)
{
return (double)element.GetValue(BorderRoundnessProperty);
}
}
How I am using it in the page:
<customcontrols:AwakeButton Style="{StaticResource AwakeButton}" Margin="142,115,0,0" Width="136" Height="167" BorderRoundness="5">
You have to bind the BorderRoundness to the parent AwakeButton, otherwise it is resolved using the current DataContext, which does not contain this property. Furthermore, if you derive from Button, you do not have to make the dependency property attached, you could just register a normal one using the Register(...) method. Also make DPs static and readonly.
<Setter Property="CornerRadius" Value="{Binding BorderRoundness, RelativeSource={RelativeSource AncestorType={x:Type local:AwakeButton}}}" />
If you do not change anything special about the button, you could also create attached properties instead of a dedicated sub type just for exposing a BorderRoundness property.
public static class ButtonProperties
{
public static readonly DependencyProperty BorderRoundnessProperty =
DependencyProperty.RegisterAttached("BorderRoundness", typeof(double), typeof(ButtonProperties));
public static void SetBorderRoundness(UIElement element, double value)
{
element.SetValue(BorderRoundnessProperty, value);
}
public static double GetBorderRoundness(UIElement element)
{
return (double)element.GetValue(BorderRoundnessProperty);
}
}
You can refer to the BorderRoundness using attached property binding syntax (parentheses).
<Style x:Key="AwakeButton" TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="local:ButtonProperties.BorderRoundness" Value="4.0"/>
<Style.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="{Binding (local:ButtonProperties.BorderRoundness), RelativeSource={RelativeSource AncestorType={x:Type Button}}}" />
</Style>
</Style.Resources>
</Style>
You use regular button now with the newly created attached border roundness property.
<Button Grid.Row="0" Style="{StaticResource AwakeButton}" Margin="142,115,0,0" Width="136" Height="167" local:ButtonProperties.BorderRoundness="5"/>
The roundness is applied as CornerRadius to the Border of the Button. The Border is defined in the ControlTemplate of the Button. The ControlTemplate defines the appearance of a control.
In other words, you need to delegate the property values to the related elements in the ControlTemplate.
To delegate the values to the ControlTemplate, you have to override this template and bind the templated parents properties to the template elements:
In your AwakeButton define the BorderRoundness property as simple DependencyProperty (not attached) and the override the default style definition, so that the AwakeButton will use its own default Style. This way the Button is reusable withgout having to redefine the Style each time you weant to use it, which is especially important wjen you publish your project as library:
AwakeButton.cs
public class AwakeButton : Button
{
public static readonly DependencyProperty BorderRoundnessProperty = DependencyProperty.Register(
"BorderRoundness",
typeof(Thickness),
typeof(AwakeButton),
new PropertyMetadata(default(Thickness)));
public Thickness DestinationPath
{
get => (Thickness) GetValue(AwakeButton.BorderRoundnessProperty);
set => SetValue(AwakeButton.BorderRoundnessProperty, value);
}
static AwakeButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(AwakeButton), new FrameworkPropertyMetadata(typeof(AwakeButton)));
}
}
Generic.xaml.cs
This file is located in the Themes folder and contains all default styles. WPF will automatically check this file for a default style and apply it if no other Style override was found.
<Style TargetType="AwakeButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="AwakeButton">
<Border BorderBrush={TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding BorderRoundness}">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Stayle>
Example
<Grid>
<AwakeButton BorderRoundness="8" />
</Grid>
But if you want to make it really general, using an attached property, you have to make a attached behavior. The following code works with every DependencyObject that contains a Border as child in its visual tree:
class Element : DependencyObject
{
#region CornerRoundness attached property
public static readonly DependencyProperty CornerRoundnessProperty = DependencyProperty.RegisterAttached(
"CornerRoundness",
typeof(CornerRadius),
typeof(Element),
new PropertyMetadata(default(CornerRadius), Element.OnCornerRoundnessChanged));
public static void SetCornerRoundness(DependencyObject attachingElement, CornerRadius value) =>
attachingElement.SetValue(Element.CornerRoundnessProperty, value);
public static CornerRadius GetCornerRoundness(DependencyObject attachingElement) =>
(CornerRadius) attachingElement.GetValue(Element.CornerRoundnessProperty);
#endregion CornerRoundness attached property
private static void OnCornerRoundnessChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e)
{
if (Element.TryFindVisualChildElement(attachingElement, out Border elementBorder))
{
elementBorder.CornerRadius = (CornerRadius) e.NewValue;
}
}
public static bool TryFindVisualChildElement<TChild>(DependencyObject parent, out TChild resultElement)
where TChild : DependencyObject
{
resultElement = null;
if (parent is Popup popup)
{
parent = popup.Child;
if (parent == null)
{
return false;
}
}
for (var childIndex = 0; childIndex < VisualTreeHelper.GetChildrenCount(parent); childIndex++)
{
DependencyObject childElement = VisualTreeHelper.GetChild(parent, childIndex);
if (childElement is TChild child)
{
resultElement = child;
return true;
}
if (Element.TryFindVisualChildElement(childElement, out resultElement))
{
return true;
}
}
return false;
}
}
Example
<StackPanel>
<Button Element.CornerRoundness="8" />
<ToggleButton Element.CornerRoundness="8" />
</StackPanel>

WPF&MVVM: access to Controls from RelayCommand()

I need both operating by mouse clicking and operating by hotkeys in my WPF application. User's actions affects on both data and appearance of application controls.
For example, the following app will send data to tea machine. You can select the tea brand, type (hot or cold) and optional ingredients: milk, lemon and syrup.
Not good from the point of view of UI design, but just example:
If to click the dropdown menu or input Ctrl+B, the list of select options will appear.
If to click the "Hot" button on input Ctrl+T, button becomes blue and text becomes "Cold". If to click or input Ctrl+T again, button becomes orange and text becomes to "Hot" again.
If to click optional ingredient button or input respective shortcut, button's background and text becomes gray (it means "unselected"). Same action will return the respective button to active state.
If don't use MVVM and don't define shortcuts, the logic will be relatively simple:
Tea tea = new Tea(); // Assume that default settings avalible
private void ToggleTeaType(object sender, EventArgs e){
// Change Data
if(tea.getType().Equals("Hot")){
tea.setType("Cold");
}
else{
tea.setType("Hot");
}
// Change Button Appearence
ChangeTeaTypeButtonAppearence(sender, e);
}
private void ChangeTeaTypeButtonAppearence(object sender, EventArgs e){
Button clickedButton = sender as Button;
Style hotTeaButtonStyle = this.FindResource("TeaTypeButtonHot") as Style;
Style coldTeaButtonStyle = this.FindResource("TeaTypeButtonCold") as Style;
if (clickedButton.Tag.Equals("Hot")) {
clickedButton.Style = coldTeaButtonStyle; // includes Tag declaration
clickedButton.Content = "Cold";
}
else (clickedButton.Tag.Equals("Cold")) {
clickedButton.Style = hotTeaButtonStyle; // includes Tag declaration
clickedButton.Content = "Hot";
}
}
// similarly for ingredients toggles
XAML:
<Button Content="Hot"
Tag="Hot"
Click="ToggleTeaType"
Style="{StaticResource TeaTypeButtonHot}"/>
<Button Content="Milk"
Tag="True"
Click="ToggleMilk"
Style="{StaticResource IngredientButtonTrue}"/>
<Button Content="Lemon"
Tag="True"
Click="ToggleLemon"
Style="{StaticResource IngredientButtonTrue}"/>
<Button Content="Syrup"
Tag="True"
Click="ToggleSyrup"
Style="{StaticResource IngredientButtonTrue}"/>
I changed my similar WPF project to MVVM because thanks to commands it's simple to assign the shortcuts:
<Window.InputBindings>
<KeyBinding Gesture="Ctrl+T" Command="{Binding ToggleTeaType}" />
</Window.InputBindings>
However, now it's a problem how to set the control's appearance. The following code is invalid:
private RelayCommand toggleTeaType;
public RelayCommand ToggleTeaType {
// change data by MVVM methods...
// change appearence:
ChangeTeaTypeButtonAppearence(object sender, EventArgs e);
}
I need the Relay Commands because I can bind it to both buttons and shortcuts, but how I can access to View controls from RelayCommand?
You should keep the viewmodel clean of view specific behavior. The viewmodel should just provide an interface for all relevant settings, it could look similar to the following (BaseViewModel would contain some helper methods to implement INotifyPropertyChanged etc.):
public class TeaConfigurationViewModel : BaseViewModel
{
public TeaConfigurationViewModel()
{
_TeaNames = new string[]
{
"Lipton",
"Generic",
"Misc",
};
}
private IEnumerable<string> _TeaNames;
public IEnumerable<string> TeaNames
{
get { return _TeaNames; }
}
private string _SelectedTea;
public string SelectedTea
{
get { return _SelectedTea; }
set { SetProperty(ref _SelectedTea, value); }
}
private bool _IsHotTea;
public bool IsHotTea
{
get { return _IsHotTea; }
set { SetProperty(ref _IsHotTea, value); }
}
private bool _WithMilk;
public bool WithMilk
{
get { return _WithMilk; }
set { SetProperty(ref _WithMilk, value); }
}
private bool _WithLemon;
public bool WithLemon
{
get { return _WithLemon; }
set { SetProperty(ref _WithLemon, value); }
}
private bool _WithSyrup;
public bool WithSyrup
{
get { return _WithSyrup; }
set { SetProperty(ref _WithSyrup, value); }
}
}
As you see, there is a property for each setting, but the viewmodel doesn't care about how the property is assigned.
So lets build some UI. For the following example, generally suppose xmlns:local points to your project namespace.
I suggest utilizing a customized ToggleButton for your purpose:
public class MyToggleButton : ToggleButton
{
static MyToggleButton()
{
MyToggleButton.DefaultStyleKeyProperty.OverrideMetadata(typeof(MyToggleButton), new FrameworkPropertyMetadata(typeof(MyToggleButton)));
}
public Brush ToggledBackground
{
get { return (Brush)GetValue(ToggledBackgroundProperty); }
set { SetValue(ToggledBackgroundProperty, value); }
}
// Using a DependencyProperty as the backing store for ToggledBackground. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ToggledBackgroundProperty =
DependencyProperty.Register("ToggledBackground", typeof(Brush), typeof(MyToggleButton), new FrameworkPropertyMetadata());
}
And in Themes/Generic.xaml:
<Style TargetType="{x:Type local:MyToggleButton}" BasedOn="{StaticResource {x:Type ToggleButton}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyToggleButton}">
<Border x:Name="border1" BorderBrush="Gray" BorderThickness="1" Background="{TemplateBinding Background}" Padding="5">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="border1" Property="Background" Value="{Binding ToggledBackground,RelativeSource={RelativeSource TemplatedParent}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now, build the actual window content using this toggle button. This is just a rough sketch of your desired UI, containing only the functional controls without labels and explanation:
<Grid x:Name="grid1">
<StackPanel>
<StackPanel Orientation="Horizontal">
<ComboBox
x:Name="cb1"
VerticalAlignment="Center"
IsEditable="True"
Margin="20"
MinWidth="200"
ItemsSource="{Binding TeaNames}"
SelectedItem="{Binding SelectedTea}">
</ComboBox>
<local:MyToggleButton
x:Name="hotToggle"
IsChecked="{Binding IsHotTea}"
VerticalAlignment="Center"
Margin="20" MinWidth="60"
Background="AliceBlue" ToggledBackground="Orange">
<local:MyToggleButton.Style>
<Style TargetType="{x:Type local:MyToggleButton}">
<Setter Property="Content" Value="Cold"/>
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Content" Value="Hot"/>
</Trigger>
</Style.Triggers>
</Style>
</local:MyToggleButton.Style>
</local:MyToggleButton>
</StackPanel>
<StackPanel Orientation="Horizontal">
<local:MyToggleButton
x:Name="milkToggle"
Content="Milk"
IsChecked="{Binding WithMilk}"
VerticalAlignment="Center"
Margin="20" MinWidth="60"
Background="WhiteSmoke" ToggledBackground="LightGreen"/>
<local:MyToggleButton
x:Name="lemonToggle"
Content="Lemon"
IsChecked="{Binding WithLemon}"
VerticalAlignment="Center"
Margin="20" MinWidth="60"
Background="WhiteSmoke" ToggledBackground="LightGreen"/>
<local:MyToggleButton
x:Name="syrupToggle"
Content="Syrup"
IsChecked="{Binding WithSyrup}"
VerticalAlignment="Center"
Margin="20" MinWidth="60"
Background="WhiteSmoke" ToggledBackground="LightGreen"/>
</StackPanel>
</StackPanel>
</Grid>
Notice the style trigger to change the button content between Hot and Cold.
Initialize the datacontext somewhere (eg. in the window constructor)
public MainWindow()
{
InitializeComponent();
grid1.DataContext = new TeaConfigurationViewModel();
}
At this point, you have a fully functional UI, it will work with the default mouse and keyboard input methods, but it won't yet support your shortcut keys.
So lets add the keyboard shortcuts without destroying the already-working UI. One approach is, to create and use some custom commands:
public static class AutomationCommands
{
public static RoutedCommand OpenList = new RoutedCommand("OpenList", typeof(AutomationCommands), new InputGestureCollection()
{
new KeyGesture(Key.B, ModifierKeys.Control)
});
public static RoutedCommand ToggleHot = new RoutedCommand("ToggleHot", typeof(AutomationCommands), new InputGestureCollection()
{
new KeyGesture(Key.T, ModifierKeys.Control)
});
public static RoutedCommand ToggleMilk = new RoutedCommand("ToggleMilk", typeof(AutomationCommands), new InputGestureCollection()
{
new KeyGesture(Key.M, ModifierKeys.Control)
});
public static RoutedCommand ToggleLemon = new RoutedCommand("ToggleLemon", typeof(AutomationCommands), new InputGestureCollection()
{
new KeyGesture(Key.L, ModifierKeys.Control)
});
public static RoutedCommand ToggleSyrup = new RoutedCommand("ToggleSyrup", typeof(AutomationCommands), new InputGestureCollection()
{
new KeyGesture(Key.S, ModifierKeys.Control)
});
}
You can then bind those commands to appropriate actions in your main window:
<Window.CommandBindings>
<CommandBinding Command="local:AutomationCommands.OpenList" Executed="OpenList_Executed"/>
<CommandBinding Command="local:AutomationCommands.ToggleHot" Executed="ToggleHot_Executed"/>
<CommandBinding Command="local:AutomationCommands.ToggleMilk" Executed="ToggleMilk_Executed"/>
<CommandBinding Command="local:AutomationCommands.ToggleLemon" Executed="ToggleLemon_Executed"/>
<CommandBinding Command="local:AutomationCommands.ToggleSyrup" Executed="ToggleSyrup_Executed"/>
</Window.CommandBindings>
and implement the appropriate handler method for each shortcut in the window code behind:
private void OpenList_Executed(object sender, ExecutedRoutedEventArgs e)
{
FocusManager.SetFocusedElement(cb1, cb1);
cb1.IsDropDownOpen = true;
}
private void ToggleHot_Executed(object sender, ExecutedRoutedEventArgs e)
{
hotToggle.IsChecked = !hotToggle.IsChecked;
}
private void ToggleMilk_Executed(object sender, ExecutedRoutedEventArgs e)
{
milkToggle.IsChecked = !milkToggle.IsChecked;
}
private void ToggleLemon_Executed(object sender, ExecutedRoutedEventArgs e)
{
lemonToggle.IsChecked = !lemonToggle.IsChecked;
}
private void ToggleSyrup_Executed(object sender, ExecutedRoutedEventArgs e)
{
syrupToggle.IsChecked = !syrupToggle.IsChecked;
}
Again, remember this whole input binding thing is purely UI related, it is just an alternative way to change the displayed properties and the changes will be transferred to the viewmodel with the same binding as if the user clicks the button by mouse. There is no reason to carry such things into the viewmodel.
how I can access to View controls from RelayCommand?
You shouldn't. The whole point of MVVM (arguably) is to separate concerns. The 'state' that the ViewModel contains is rendered by the View (controls). The ViewModel/logic should never directly adjust the view - as this breaks the separation of concerns and closely couples the logic to the rendering.
What you need is for the view to render how it wants to display the state in the View Model.
Typically, this is done by bindings. As example: Rather than the ViewModel grabbing a text box reference and setting the string: myTextBox.SetText("some value"), we have the view bind to the property MyText in the view model.
It's the view's responsibility to decide how to show things on the screen.
That's all well and good, but how? I suggest, if you want to do this change using styles like you describe, I'd try using a converter that converts the using a binding to ViewModel state (Say, an enum property Hot or Cold):
<Button Content="Hot"
Tag="Hot"
Click="ToggleTeaType"
Style="{Binding TeaType, Converter={StaticResource TeaTypeButtonStyleConverter}}"/>
Note, we're using WPF's bindings. The only reference we've got tot he view model is through it's property TeaType.
Defined in your static resources, we have the converter:
<ResourceDictionary>
<Style x:Key="HotTeaStyle"/>
<Style x:Key="ColdTeaStyle"/>
<local:TeaTypeButtonStyleConverter
x:Key="TeaTypeButtonStyleConverter"
HotStateStyle="{StaticResource HotTeaStyle}"
ColdStateStyle="{StaticResource ColdTeaStyle}"/>
</ResourceDictionary>
And have the logic for converting from the TeaType enum to a Style in this:
public enum TeaType
{
Hot, Cold
}
class TeaTypeButtonStyleConverter : IValueConverter
{
public Style HotStateStyle { get; set; }
public Style ColdStateStyle { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
TeaType teaType = (TeaType)value;
if (teaType == TeaType.Hot)
{
return HotStateStyle;
}
else if (teaType == TeaType.Cold)
{
return ColdStateStyle;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
It could be made more generic and re-usable.
You should also take a look at toggle buttons, they deal with this kind of thing internally.

Get Treeviewitem from Menu Context. WPF XAML MVVM

Good day.
I have created a TreeViewHelper Class in order to trigger events and pass through datacontext to functions. However. Id like to select the treeviewitem when right clicking.
My solution follows the MVVM pattern as close as possible. So if possible i'd like to avoid the Trigger Event feature that is provided.
Id love for some advice on how to tackle this issue.
here is the treeviewhelper.cs class
public static class TreeViewHelper
{
#region SelectedItem
public static object GetSelectedItem(DependencyObject obj)
{
return (object)obj.GetValue(SelectedItemProperty);
}
public static void SetSelectedItem(DependencyObject obj, object value)
{
obj.SetValue(SelectedItemProperty, value);
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.RegisterAttached("SelectedItem", typeof(object), typeof(TreeViewHelper), new UIPropertyMetadata(null, SelectedItemChanged));
private static void SelectedItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs dpea)
{
if (!(obj is TreeView) || dpea.NewValue == null)
return;
var view = obj as TreeView;
view.SelectedItemChanged += (sender, e) => SetSelectedItem(view, e.NewValue);
var command = (ICommand)(view as DependencyObject).GetValue(SelectedItemChangedProperty);
if (command != null)
{
if (command.CanExecute(null))
command.Execute(new DependencyPropertyEventArgs(dpea));
}
}
#endregion
#region Selected Item Changed
public static ICommand GetSelectedItemChanged(DependencyObject obj)
{
return (ICommand)obj.GetValue(SelectedItemProperty);
}
public static void SetSelectedItemChanged(DependencyObject obj, ICommand value)
{
obj.SetValue(SelectedItemProperty, value);
}
public static readonly DependencyProperty SelectedItemChangedProperty =
DependencyProperty.RegisterAttached("SelectedItemChanged", typeof(ICommand), typeof(TreeViewHelper));
#endregion
#region Event Args
public class DependencyPropertyEventArgs : EventArgs
{
public DependencyPropertyChangedEventArgs DependencyPropertyChangedEventArgs { get; private set; }
public DependencyPropertyEventArgs(DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
this.DependencyPropertyChangedEventArgs = dependencyPropertyChangedEventArgs;
}
}
#endregion
}
Here is the XAML Code (shortened)
<TreeView x:Name="TestPlanTreeView"
ItemsSource="{Binding Data.TreeViewCollection}"
utils:TreeViewHelper.SelectedItem="{Binding CurrSelItem}"
utils:TreeViewHelper.SelectedItemChanged="{Binding SelItemChgCmd}">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu ItemsSource="{Binding MenuCollection, RelativeSource={RelativeSource Self}}">
<ContextMenu.Resources>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</ContextMenu.Resources>
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Header}"/>
</HierarchicalDataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Nodes}">
<TextBlock Text="{Binding Header}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
This is how i trigger a function when selecting a new item.
SelItemChgCmd = new RelayCommand<DependencyPropertyEventArgs>(Data.TreeViewItemSelected);
CurrSelItem = new object();
public void TreeViewItemSelected(DependencyPropertyEventArgs dpe)
{
TreeViewItem selectedTreeViewItem = dpe.DependencyPropertyChangedEventArgs.NewValue as TreeViewItem;
ListViewCollection = BuildListViewCollection(selectedTreeViewItem);
}
What I would love to do, is to be able to select the treeviewitem before right clicking on the object. However, I am not sure how to do that with my given code without hacking it.
My original thoughts were to create a property in the treeviewhelper for PreviewRightMouseButtonUp and have that trigger the SelectedItem event, but i then run into issues with data.
Since the PreviewRightMouseButtonUp does not hold the same method types as the selectedItem Changed event, then i run into these data issues.
Id really appreciate some thoughts on this matter. Its been blocking me for a while now.

Update Normal Property in Dependency property/AttachedProperty,

I am trying to bind a normal property of AvalonDock,
xmlns:xcad="http://schemas.xceed.com/wpf/xaml/avalondock"
<xcad:LayoutAnchorable Title="Folder" CanHide="{Binding IsHideExplorerView}">
<Views:ExplorerView DataContext="{Binding ExplorerViewModel}"/>
</xcad:LayoutAnchorable>
Here CanHide is a Normal property, if trying to bind will throw the exception like
A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
My question is, Is it possible any way to make a normal property to override DependencyProperty to make it Bindable.
Edit
Added a class which inherit LayoutAnchorable but PropertyChangedCallback of DependencyProperty Never calls.
public class ExtendedAnchorableItem : LayoutAnchorable
{
public static readonly DependencyProperty IsCanHideProperty =
DependencyProperty.RegisterAttached("IsCanHide", typeof(bool), typeof(ExtendedAnchorableItem),
new FrameworkPropertyMetadata((bool)false,
new PropertyChangedCallback(OnCanHideChanged)));
public bool IsCanHide
{
get { return (bool)GetValue(IsCanHideProperty); }
set { SetValue(IsCanHideProperty, value);
this.IsVisible = value; // No effect.
}
}
private static void OnCanHideChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ExtendedAnchorableItem)d).Hide();
}
}
XAML
<xcad:LayoutAnchorablePane>
<Utility:ExtendedAnchorableItem IsCanHide="{Binding IsHideExplorer}">
<Views:ExplorerView DataContext="{Binding ExplorerViewModel}"/>
</Utility:ExtendedAnchorableItem>
</xcad:LayoutAnchorablePane>
Similarly i have tried creating an AttachedProperty which can hook it to LayoutAnchorable but PropertyChangedCallback Never get called click here for a new question i have posted.
Any Help guys ?
I did and example previously in my case i need to create new button with 2 images one when the button is available and the other one when it's disabled, to do that first i created new user control named "MyButton" my xaml was like this
<Button ToolTip="{Binding ButtonLabel,RelativeSource={RelativeSource AncestorType=UserControl,Mode=FindAncestor},UpdateSourceTrigger=PropertyChanged}"
Command="{Binding ButtonCommand,RelativeSource={RelativeSource AncestorType=UserControl,Mode=FindAncestor},UpdateSourceTrigger=PropertyChanged}"
Cursor="Hand" VerticalAlignment="Center" >
<Button.Template>
<ControlTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="45"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Name="ButtonImage" IsEnabled="{Binding Path=IsEnabled,RelativeSource={RelativeSource AncestorType=Button,Mode=FindAncestor}}" >
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Source" Value="{Binding ActiveImage,RelativeSource={RelativeSource AncestorType=UserControl,Mode=FindAncestor},UpdateSourceTrigger=PropertyChanged}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Source" Value="{Binding DeactiveImage,RelativeSource={RelativeSource AncestorType=UserControl,Mode=FindAncestor},UpdateSourceTrigger=PropertyChanged}"/>
</Trigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<Label Name="LabelContent" Content="{Binding ButtonLabel,RelativeSource={RelativeSource AncestorType=UserControl,Mode=FindAncestor},UpdateSourceTrigger=PropertyChanged}"
Grid.Column="1" IsEnabled="{Binding Path=IsEnabled,RelativeSource={RelativeSource AncestorType=Button,Mode=FindAncestor}}" VerticalContentAlignment="Center" />
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
then i added dependency Properties for ActiveImage and DeactiveImage using this code
public static DependencyProperty activeImage =
DependencyProperty.Register("ActiveImage", typeof(type of this property like "string"), typeof(type of the custom control that you need like "MyButton"), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string ActiveImage
{
get { return (string)GetValue(activeImage); }
set { SetValue(activeImage, value); }
}
then i used this new control in my project
<custom:MyButton ButtonCommand="{Binding DecreaseImagesCount}" ButtonLabel="ZoomIn" ActiveImage="/Images/ActiveImages/ZoomIn.png" DeactiveImage="/Images/GrayImages/ZoomIn.png"
Grid.Column="2" Margin="3,4" />
notice that i can do binding the path for Button Image now
If it is enough for you to just set that property from your view model then you could use an attached behavior.
Just create a new class and add an attached property like this (I did not really test this, since I actually do not have AvalonDock at hand, but you should get the idea):
public class YourBehavior
{
public static readonly DependencyProperty YourCanHideProperty = DependencyProperty.RegisterAttached(
"YourCanHide",
typeof(bool),
typeof(LayoutAnchorable),
new PropertyMetadata(YourCanHidePropertyChanged));
private static void YourCanHidePropertyChanged(
DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
LayoutAnchorable control = dependencyObject as LayoutAnchorable;
if (control != null)
{
control.CanHide = e.NewValue as bool;
}
}
public static bool GetYourCanHideProperty(LayoutAnchorablewindow)
{
return window.GetValue(YourProperty) as bool?;
}
public static void SetYourCanHideProperty(LayoutAnchorable control, bool value)
{
window.SetValue(YourProperty, value);
}
}
Now you should be able to use that behavior like this:
<xcad:LayoutAnchorable Title="Folder" namespacealias:YourBehavior.YourCanHideProperty="{Binding IsHideExplorerView}"/>
If you want to have it working in both directions just check out the attached Blend behaviors.
Yes, you can do it.. you need to implement INotifypropertyChanged interface and raise a ProprtyChanged Event inside the property setter. After changing the property to a DependencyProperty, you will get the notification mechanism, so the property change is propagated to the target (in this case xcad) .
you can find lot of examples implementing the INotifyPropertyChanged..

Saving a WPF canvas as an image following MVVM Pattern

I have a canvas, e.g. similar to this solution or many others using the ItemsControl.
Now I want a button which should be bound to an ICommand. This command should call a method of ViewModel class which can save the image.
The saving method is clear, but how do I do the binding following the MVVM pattern?
You could pass the Canvas to the ViewModel's Save method using a CommandParameter
<Button Content="Save"
Command="{Binding SaveCanvasCommand}"
CommandParameter="{Binding ElenementName=myCanvas}" ?>
<Canvas x:Name="myCanvas">
<!-- Stuff to save -->
</Canvas>
And somewhere in you ViewModel or Command you'd have
void SaveCanvasCommandExecute(object parameter)
{
UIElement toSave = (UIElement)parameter;
//.. You'd probably use RenderTargetBitmap here to save toSave.
}
If you don't want to reference UI elements in your ViewModel you could use an attached behaviour:
internal static class Behaviours
{
public static readonly DependencyProperty SaveCanvasProperty =
DependencyProperty.RegisterAttached("SaveCanvas", typeof(bool), typeof(Behaviours),
new UIPropertyMetadata(false, OnSaveCanvas));
public static void SetSaveCanvas(DependencyObject obj, bool value)
{
obj.SetValue(SaveCanvasProperty, value);
}
public static bool GetSaveCanvas(DependencyObject obj)
{
return (bool)obj.GetValue(SaveCanvasProperty);
}
private static void OnSaveCanvas(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
// Save code.....
}
}
}
Then in your ViewModel you have your Command that sets a property, also on your ViewModel:
public ICommand SaveCanvasCommand
{
get
{
if (_saveCanvasCommand == null)
_saveCanvasCommand = new RelayCommand(() => { IsSaveCanvas = true; });
return _saveCanvasCommand;
}
}
And the property which is bound to your View:
public bool IsSaveCanvas
{
get { return _isSaveCanvas; }
set
{
_isSaveCanvas = value;
RaisePropertyChanged("IsSaveCanvas");
}
}
Then hooking it all up in the Xaml looks like this:
Add a Trigger on the Control that binds the value of your ViewModel property to your attached behaviour:
<UserControl.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSaveCanvas}" Value="True">
<Setter Property="wpfApplication1:Behaviours.SaveCanvas" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsSaveCanvas}" Value="False">
<Setter Property="wpfApplication1:Behaviours.SaveCanvas" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
And then bind your Button / MenuItem to the ViewModels Save Command:
<Canvas.ContextMenu>
<MenuItem Header="Save" Command="{Binding SaveCanvasCommand}"/>
</Canvas.ContextMenu>

Categories