I am working on expression Blend for VS2015, I have aListBox binded to an ObservableCollection of custom objects. Those objects expose Properties that arise the NotifyPropertyChanged, and everything works nice.
I can bind parts if the ItemTemplate to those Properties and my list work nice but what I want to do is to set the VisualState according to a certain bool (already configured or not). I also created some events (configured, confLost) and tried to target those events in the triggers panel but .. nothing worked.
How do I bind VisualStates to members of the bound object ??
ItemTemplate property works like any other DependencyProperty, it can be set/reset anytime and it's visual impact will be reflected on UI. see below example where I have bound a bool value to ToggleButton state and ItemControl's ItemTemplate is changed accordingly rendering different visual.
Update: I designed a Device class that has device name and it's state to make a similar situation. And another class MyVisualStateManager to create a bindable property. Cause VisualStateManager class doesn't expose any property to bind directly. code is as below:
XMAL
<Window x:Class="WpfStackOverflowTempProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}"
xmlns:local="clr-namespace:WpfStackOverflowTempProject"
>
<ItemsControl ItemsSource="{Binding list}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:UserControl1 DataContext="{Binding Name}" Width="200" BorderBrush="Black" BorderThickness="2" Padding="2">
<local:UserControl1.Style>
<Style TargetType="{x:Type local:UserControl1}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext.DeviceState}" Value="0">
<Setter Property="local:MyVisualStateManager.VisualState" Value="State1" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext.DeviceState}" Value="1">
<Setter Property="local:MyVisualStateManager.VisualState" Value="State2" />
</DataTrigger>
</Style.Triggers>
</Style>
</local:UserControl1.Style>
</local:UserControl1>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
UserControl:
<UserControl x:Class="WpfStackOverflowTempProject.UserControl1"
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">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="Common">
<VisualState x:Name="State1">
<Storyboard>
<DoubleAnimation To="1" Duration="0:00:2" Storyboard.TargetName="State1Panel" Storyboard.TargetProperty="(UIElement.Opacity)" />
<DoubleAnimation To="0" Duration="0:00:3" Storyboard.TargetName="State2Panel" Storyboard.TargetProperty="(UIElement.Opacity)" />
</Storyboard>
</VisualState>
<VisualState x:Name="State2">
<Storyboard>
<DoubleAnimation To="0" Duration="0:00:3" Storyboard.TargetName="State1Panel" Storyboard.TargetProperty="(UIElement.Opacity)" />
<DoubleAnimation To="1" Duration="0:00:2" Storyboard.TargetName="State2Panel" Storyboard.TargetProperty="(UIElement.Opacity)" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border Name="State2Panel" Background="Green" Opacity="0"/>
<Border Name="State1Panel" Background="Red" Opacity="1"/>
<TextBlock Text="{Binding Path=.}" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
DataContext:
public partial class MainWindow : Window
{
public MainWindow()
{
list = new List<Device>();
list.Add(new Device() {Name="Device 1",DeviceState = 0 });
list.Add(new Device() { Name = "Device 2", DeviceState = 1 });
list.Add(new Device() { Name = "Device 3", DeviceState = 0 });
list.Add(new Device() { Name = "Device 4", DeviceState = 2 });
list.Add(new Device() { Name = "Device 5", DeviceState = 1 });
InitializeComponent();
}
public List<Device> list { get; set; }
}
public class Device : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
updateProperty("Name");
}
}
private int deviceState;
public int DeviceState
{
get { return deviceState; }
set
{
deviceState = value;
updateProperty("DeviceState");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void updateProperty(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
Helper Class: This class exposes an attached property VisualState that could be bound to any value in xaml.
public class MyVisualStateManager
{
public static string GetVisualState(DependencyObject obj)
{
return (string)obj.GetValue(VisualStateProperty);
}
public static void SetVisualState(DependencyObject obj, string value)
{
obj.SetValue(VisualStateProperty, value);
}
// Using a DependencyProperty as the backing store for VisualState. This enables animation, styling, binding, etc...
public static readonly DependencyProperty VisualStateProperty =
DependencyProperty.RegisterAttached("VisualState", typeof(string), typeof(MyVisualStateManager), new PropertyMetadata(new PropertyChangedCallback(VisualStateChanged)));
public static void VisualStateChanged(DependencyObject Do, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null)
{
string state = e.NewValue.ToString();
var control = Do as FrameworkElement;
VisualStateManager.GoToState(control, state, true);
}
}
}
Output
Different Item representing different devices and visual is changed on basis of theirDevicestateproperty which causes aTriggerto get executed inUserControl1.
Although Kylo's solution would provably work, the people at Microsoft already worked a code-free, 3-clicks-away solution for such a simple action to do.
Solution is on Behaviors, there is one behavior called GoToStateAction you have to add one of them to your control and there you can set your trigger (that can be set as DataTrigger). In my case I binded to a property of the type enum.
Then you can set the Comparison and the value (equals to "ReadyToUse")
Then as an outcome of the comparison you can trigger a state change for a particular object, you set your object, and you select the state from a nice combobox. There is even a checbox for using your transitions.
I mean in the user interface of Blend, where to click to get the conditional working,
In Blend look for the Objects and Timeline panel.
Select the control in your defined ItemTemplate (you have set one up right?) which you want to have the state changes. In Kylo's example above we would have selected the TextBox.
Right click and select Edit Style the either create a new style (most likely) or Edit a Copy which works of any existing inherited styles.
From there one can work on the properties tab to change specifics. Most likely you will be working directly in the xaml to do specific operations.
Even though these docs for Version 2, they still apply, if nothing else can give you an overview of Blend
Create a style resource
Style and template overview
Related
I created a WPF Popup which contains a grid with border.
There is some animation associated with the border which I want to be triggered every time the Popup opens.
Currently the code is like this
<Popup x:Name="myPopUp" >
<Border x:Name="myBorder" >
<Border.Triggers>
<EventTrigger RoutedEvent="Popup.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="myBorder"
Storyboard.TargetProperty="Height"
From="10" To="255" Duration="0:0:0.20" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
<Grid />
</Border>
</Popup>
As per the code the border shows up the animation for the first time the popup opens.
What change do I need to make to trigger the border animation every time the Popup opens?
As per suggestions given here and a little bit expireince now (I asked this a year back :) ), I could figure out the solution.
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" >
<Window.Resources>
<Style x:Key="popupStyle" TargetType="{x:Type Popup}" >
<Style.Triggers>
<Trigger Property="IsOpen" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="Height"
From="10" To="255" Duration="0:0:0.20" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Button Width="100" Height="100" Click="Button_Click"></Button>
<Popup Name="popUp" Width="100" Height="100" Style="{StaticResource popupStyle}" >
<Border x:Name="myBorder" Background="Blue"/>
</Popup>
</Grid>
and a sample code behind to trigger the popup..
private void Button_Click(object sender, RoutedEventArgs e)
{
popUp.PlacementTarget = (Button)sender;
popUp.IsOpen = true;
}
Although I can only animate the Popup and not the Border here, it pretty much gives the same result.
I'm not sure if the popup gets focus when it opens, but you could use the GotFocus event if it does. Alternatively, you could try using a datatrigger on the is IsOpen property. I think you'd have to put that in a style though instead of inline.
You can achieve this by listening to the IsOpen dependency property like
public MainWindow()
{
InitializeComponent();
//// Listening to the IsOpen dependency property of the Popup.
this.SetBinding(PopupIsOpenProperty, new Binding() { Source = this.popupContainer, Path = new PropertyPath("IsOpen") });
}
/// <summary>
/// Gets or sets a value indicating whether [popup is open].
/// </summary>
/// <value><c>true</c> if [popup is open]; otherwise, <c>false</c>.</value>
public bool PopupIsOpen
{
get { return (bool)GetValue(PopupIsOpenProperty); }
set { SetValue(PopupIsOpenProperty, value); }
}
// Using a DependencyProperty as the backing store for PopupIsOpen. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PopupIsOpenProperty =
DependencyProperty.Register("PopupIsOpen", typeof(bool), typeof(MainWindow), new PropertyMetadata(false,
(dependencyObject, e) =>
{
var mainWindow = (MainWindow)dependencyObject;
if (mainWindow != null &&
(bool)e.NewValue == true)
{
//// Raise your event here... like
//// mainWindow.RaisePopupOpened();
System.Diagnostics.Debug.WriteLine("Popup Open Triggered");
}
}));
private void button_MouseLeave(object sender, MouseEventArgs e)
{
this.popupContainer.IsOpen = false;
}
private void button_MouseMove(object sender, MouseEventArgs e)
{
//// Setting the popup position
var p = e.GetPosition(sender as UIElement);
this.popupContainer.HorizontalOffset = p.X;
this.popupContainer.VerticalOffset = p.Y;
//// Enabling popup when it is hover on the button
this.popupContainer.IsOpen = true;
}
<!-- XAML Starts here-->
<Grid>
<Button x:Name="button1" Content="This is a sample text" MouseMove="button_MouseMove" MouseLeave="button_MouseLeave" Width="100" Height="25" />
<Popup x:Name="popupContainer" IsHitTestVisible="False" >
<Grid Background="White">
<TextBlock Text="{Binding Content, ElementName=button}" />
</Grid>
</Popup>
</Grid>
HTH
In App.xaml.cs or in another starting class instance you need add:
var field = typeof(PresentationSource).GetField("RootSourceProperty", BindingFlags.NonPublic | BindingFlags.Static);
var property = (DependencyProperty)field.GetValue(null);
property.OverrideMetadata(typeof(DependencyObject), new FrameworkPropertyMetadata(property.DefaultMetadata.DefaultValue, OnHwndSourceChanged));
Where, RootSourceProperty is private field DependecyProperty of PresentationSource. Its property use when HwndSource is created and set RootVisual. So you need just use property changed call back of RootSourceProperty:
private static void OnHwndSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
This is nice because, you can use it in your all Application and for all HwndSource (Popup, Window or Custom controls, where you are using HwndSource)
try changing your event trigger to
<EventTrigger RoutedEvent="Popup.Opened">
How can I avoid the flickering of the checked checkboxes in a WPF ListBox or ListView ? It can be reproduced with the code below by clicking on the Refresh button or by scrolling the listbox. If IsChecked is false, it does not flicker.
Window1.xaml:
<Window x:Class="WpfApplication6.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox Name="listBox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="True"
VerticalAlignment="Center"/>
<Label Padding="3"
Content="{Binding}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="Refresh"
Grid.Column="1"
VerticalAlignment="Top"
Click="Button_Click"/>
</Grid>
</Window>
Window1.xaml.cs:
using System.Windows;
namespace WpfApplication6
{
partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Button_Click(null, null);
}
void Button_Click(object sender, RoutedEventArgs e)
{
var items = new int[10000];
for (int i = 0; i < items.Length; i++)
items[i] = i + 1;
listBox.ItemsSource = items;
}
}
}
It is flickering because you are throwing out the old ItemsSource and creating a new one. This requires all of the binding to be redone, and the template displaying each item needs to be recreated. To avoid the performance overhead of recreating an entire list, just modify the individual elements in the existing ItemsSource. Then the part of the DataTemplate that is bound to the changed properties and/or items will automatically update without needing to recreate the whole list view. Doing this will eliminate the "flicker" you are seeing.
Try this for the codebehind:
public partial class MainWindow : Window
{
private ObservableCollection<object> _items;
public MainWindow()
{
InitializeComponent();
_items = new ObservableCollection<object>();
for (int i = 0; i < 10000; i++)
_items.Add(i + 1);
listBox.ItemsSource = _items;
}
void Button_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < _items.Count;i++)
{
if (!(_items[i] is int)) continue;
_items[i] = (int)_items[i] + 1;
}
}
}
Do you mean checkbox being checked? I think you need to change the animation when you check / set the checkbox checked.
It does not occur on Windows XP (that's why I think it's an animation), I haven't tested Vista :)
Good luck.
+1 to Snake who has the right answer here.
To add to this:
The CheckBox control has a control template with storyboard animation which animates the checked icon on/off when the checked state changes. The Checked state changes as you are binding to ObservableCollection and recreating the ItemsSource which is causing new Checkboxes to be created (with IsChecked=false) and bound to your ViewModel (which probably results in IsChecked=True).
To disable this 'feature' you can either modify how you fill an ObservableCollection, or if that's not possible, you can change the template / style of the Checkbox.
Just reverse engineer the ControlTemplate of Checkbox (using blend, or by using one of the WPF Themes) and find these lines. you need to set the duration of the two animations to zero
<!-- Inside the CheckBoxTemplate ControlTemplate -->
<Storyboard x:Key="CheckedTrue">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="CheckIcon"
Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00.1000000" Value="1" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="CheckedFalse">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="CheckIcon"
Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00.4000000" Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
The View and ViewModel listed below show two buttons:
when you click Show ToolBar, the toolbar fades in
when you click Hide ToolBar, the toolbar fades out
However, the following things don't work:
when the application is loaded and OnPropertyChanged("PageToolBarVisible") is fired, if the value is false then the Toolbar shows in spite of that fact (why is that?)
when the application is loaded and OnPropertyChanged("PageToolBarVisible") is fired, if the value is true then the toolbar does indeed fade in, however, this is not supposed to happen on loading but only when explicitly changed by pressing the buttons, so I change the constructor to do this: _pageToolBarVisible = "true", but then the toolbar still still fades in even though the OnPropertyChanged was never called (why is that?)
View:
<Window x:Class="TestAnim334.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:TestAnim334.Commands"
Title="Main Window" Height="400" Width="800">
<Window.Resources>
<Style x:Key="PageToolBarStyle" TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding PageToolBarVisible}" Value="true">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="Opacity"
From="0.0"
To="1.0"
Duration="0:0:1"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="Opacity"
From="1.0"
To="0.0"
Duration="0:0:1"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
<Trigger Property="Opacity" Value="0">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<DockPanel LastChildFill="False">
<StackPanel DockPanel.Dock="Top"
Margin="10">
<TextBlock Text="This is the content of the page."/>
<TextBlock Text="The ViewModel property is:"/>
<TextBlock Text="{Binding PageToolBarVisible}"/>
<Button Content="Hide ToolBar"
Width="150"
Command="{Binding HideToolBarCommand}"
HorizontalAlignment="Left"/>
<Button Content="Show ToolBar"
Width="150"
Command="{Binding ShowToolBarCommand}"
HorizontalAlignment="Left"/>
</StackPanel>
<Border Style="{StaticResource PageToolBarStyle}"
Height="40"
DockPanel.Dock="Bottom" Background="#ddd" CornerRadius="5">
<TextBlock FontSize="24" Text="This is the ToolBar text"/>
</Border>
</DockPanel>
</Window>
ViewModel:
using System.Windows.Input;
using TestAnim334.Commands;
namespace TestAnim334.ViewModels
{
public class MainViewModel : ViewModelBase
{
#region ViewModelProperty: PageToolBarVisible
private string _pageToolBarVisible;
public string PageToolBarVisible
{
get
{
return _pageToolBarVisible;
}
set
{
_pageToolBarVisible = value;
OnPropertyChanged("PageToolBarVisible");
}
}
#endregion
#region DelegateCommand: HideToolBar
private DelegateCommand hideToolBarCommand;
public ICommand HideToolBarCommand
{
get
{
if (hideToolBarCommand == null)
{
hideToolBarCommand = new DelegateCommand(HideToolBar, CanHideToolBar);
}
return hideToolBarCommand;
}
}
private void HideToolBar()
{
PageToolBarVisible = "false";
}
private bool CanHideToolBar()
{
return PageToolBarVisible == "true";
}
#endregion
#region DelegateCommand: ShowToolBar
private DelegateCommand showToolBarCommand;
public ICommand ShowToolBarCommand
{
get
{
if (showToolBarCommand == null)
{
showToolBarCommand = new DelegateCommand(ShowToolBar, CanShowToolBar);
}
return showToolBarCommand;
}
}
private void ShowToolBar()
{
PageToolBarVisible = "true";
}
private bool CanShowToolBar()
{
return PageToolBarVisible == "false";
}
#endregion
public MainViewModel()
{
PageToolBarVisible = "false";
}
}
}
Ok to answer the two parts of your question:
Why when PageToolBarVisible is fired as "False" at loading the toolbar still shows:
Your only hiding the toolbar with the animation in the "ExitActions", which aren't being hit. The logic flows as such.
if(PageToolBarVisible == true)
Run EnterActions
if(PageToolBarVisible changes to false)
Run ExitActions
if(PageToolBarVisible starts as false)
Do nothing
if(PageToolBarVisible starts as false and is set to false)
Do nothing
Conclusion, because the PageToolBarVisible is not changing from True to False ... the animation doesn't run.
Solution:
Consider having a second DataTrigger that handles the False case for your PageToolBarVisible property. Or you can set the Property to True and then False to hit your ExitActions (though I'm not sure if this would A) work or B) be a good solution)
Why setting the backing field for the Property still runs the animation when loaded:
I believe what is happening here, is that when the application loads, the Binding is checking the value of the property, if you've set the value of the backing field then it should be getting this from the "Get" on "PageToolBarVisible".
So it's not that you're triggering the OnPropertyChanged, it's that the Binding is getting the value when your app is loading
Solution:
Either rethink your logic around how your binding to the trigger and thus the animation. Or you can play with the Binding Modes, to be honest I don't think there's a mode that would satisfy the conditions you're looking for, however I might be wrong.
This should be a very simple case, but I am pulling hair trying to get it to work. Here is the setup:
I am designing an app that will have an read-only mode and edit mode for some data. So I created a User Control which is a textbox and textblock bound to the same text data and are conditionally visible based on EditableMode property (so when it's editable the textbox is shown and when it's not the textblock is shown)
Now, I want to have many of these controls in my main window and have them all bound too a single bool property. When that property is changed via a button, I want all TextBlocks to turn into TextBoxes or back.
My problem is that the control is set correctly on binding, and if I do myUserControl.Editable = true. But it doesn't change if bind it to a bool property.
Here is the code for my user control:
<UserControl x:Class="CustomerCareTool.Controls.EditableLabelControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:CustomerCareTool.Converters"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<UserControl.Resources>
<src:BoolToVisibility x:Key="boolToVisibility" Inverted="False" />
<src:BoolToVisibility x:Key="invertedBoolToVisibility" Inverted="True" />
</UserControl.Resources>
<Grid>
<TextBlock Name="textBlock" Text="{Binding Path=TextBoxValue}" Visibility="{Binding Path=EditableMode, Converter={StaticResource invertedBoolToVisibility}}"/>
<TextBox Name="textBox" Visibility="{Binding Path=EditableMode, Converter={StaticResource boolToVisibility}}">
<TextBox.Text>
<Binding Path="TextBoxValue" UpdateSourceTrigger="PropertyChanged"/>
</TextBox.Text>
</TextBox>
</Grid>
I used a converter to convert bool to visibility and inverse bool to visibility. Not sure if that's at all needed here.
And this is the code behind:
public partial class EditableLabelControl : UserControl
{
public EditableLabelControl()
{
InitializeComponent();
}
public string TextBoxValue
{
get { return (string)GetValue(TextBoxValueProperty); }
set { SetValue(TextBoxValueProperty, value); }
}
public static readonly DependencyProperty TextBoxValueProperty =
DependencyProperty.Register("TextBoxValue", typeof(string), typeof(EditableLabelControl), new UIPropertyMetadata());
public bool EditableMode
{
get { return (bool)GetValue(EditableModeProperty); }
set { SetValue(EditableModeProperty, value); }
}
public static readonly DependencyProperty EditableModeProperty =
DependencyProperty.Register("EditableMode", typeof(bool),typeof(EditableLabelControl), new UIPropertyMetadata(false, EditableModePropertyCallBack));
static void EditableModePropertyCallBack(DependencyObject property,
DependencyPropertyChangedEventArgs args)
{
var editableLabelControl = (EditableLabelControl)property;
var editMode = (bool)args.NewValue;
if (editMode)
{
editableLabelControl.textBox.Visibility = Visibility.Visible;
editableLabelControl.textBlock.Visibility = Visibility.Collapsed;
}
else
{
editableLabelControl.textBox.Visibility = Visibility.Collapsed;
editableLabelControl.textBlock.Visibility = Visibility.Visible;
}
}
}
Now in my main application I have the control added like this:
<Controls:EditableLabelControl x:Name="testCtrl" EditableMode="{Binding Path=Editable}" TextBoxValue="John Smith" Grid.Row="0"/>
For that same application the DataContext is set to self
DataContext="{Binding RelativeSource={RelativeSource Self}}"
And the code behind looks like this:
public partial class OrderInfoView : Window, INotifyPropertyChanged
{
public OrderInfoView()
{
InitializeComponent();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
Editable = !Editable;
}
private bool _editable = false;
public bool Editable
{
get
{
return _editable;
}
set
{
_editable = value;
OnPropertyChanged("Editable");
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged == null) return;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Clicking the button doesn't do anything :( I tried everything to get this to work, and no dice. Would really appreciate some help!
I tried the following, and still does not work:
public bool Editable
{
get { return (bool)GetValue(EditableProperty); }
set { SetValue(EditableProperty, value); }
}
public static readonly DependencyProperty EditableProperty =
DependencyProperty.Register("Editable", typeof(bool), typeof(OrderInfoView), new UIPropertyMetadata(false));
It looks like your solution may be more complex than necessary. If all you want to do is have a disabled TextBox look like a TextBlock then you can do this using a trigger and a template. Then you can apply that style to all text boxes.
Here's an example of that approach:
<Window x:Class="WpfApplication25.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1"
Height="300"
Width="300"
>
<Window.Resources>
<!-- Disable TextBox Style -->
<Style x:Key="_DisableTextBoxStyle" TargetType="TextBox">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<!--
Be sure to apply all necessary TemplateBindings between
the TextBox and TextBlock template.
-->
<TextBlock Text="{TemplateBinding Text}"
FontFamily="{TemplateBinding FontFamily}"
/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<TextBox IsEnabled="{Binding IsChecked, ElementName=uiIsEnabled}"
Style="{StaticResource _DisableTextBoxStyle}"
/>
<ToggleButton x:Name="uiIsEnabled" Content="Enable" IsChecked="True" />
</StackPanel>
</Window>
INotifyPropertyChanged does not work for classes that derive from DependencyObject.
Editable property in OrderInfoView must be dependency property in order for binding to work correctly, although technically your code is correct but I feel its bug in WPF that when object is dependency object it ignores INotifyPropertyChanged event because it is searching for notification in property system.
<Controls:EditableLabelControl x:Name="testCtrl"
EditableMode="{Binding Path=Editable,ElementName=userControl}" TextBoxValue="John Smith" Grid.Row="0"/>
Specify ElementName in binding tag and also name your usercontrol with x:FieldName or x:Name
I just came across this searching for something else.
Without reading your post in detail (no time atm sorry) it seems to me you're having a similar issue to the one I posted about here:
http://jonsblogat.blogspot.com/2009/11/wpf-windowdatacontext-and.html
In short, move your binding for your main window to the Grid and use a relative binding to see if that fixes your problem.
I have a problem when binding a command in a context menu on a usercontrol that is on a tab page.
The first time I use the menu (right-click on the tab) it works great, but if I switch tab the command will use the databound instance that was used the first time.
If I put a button that is bound to the command in the usercontrol it works as expected...
Can someone please tell me what I'm doing wrong??
This is a test project that exposes the problem:
App.xaml.cs:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
CompanyViewModel model = new CompanyViewModel();
Window1 window = new Window1();
window.DataContext = model;
window.Show();
}
}
Window1.xaml:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vw="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<DataTemplate x:Key="HeaderTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type vw:PersonViewModel}">
<vw:UserControl1/>
</DataTemplate>
</Window.Resources>
<Grid>
<TabControl ItemsSource="{Binding Path=Persons}"
ItemTemplate="{StaticResource HeaderTemplate}"
IsSynchronizedWithCurrentItem="True" />
</Grid>
</Window>
UserControl1.xaml:
<UserControl x:Class="WpfApplication1.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
MinWidth="200">
<UserControl.ContextMenu>
<ContextMenu >
<MenuItem Header="Change" Command="{Binding Path=ChangeCommand}"/>
</ContextMenu>
</UserControl.ContextMenu>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0">The name:</Label>
<TextBox Grid.Column="1" Text="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</UserControl>
CompanyViewModel.cs:
public class CompanyViewModel
{
public ObservableCollection<PersonViewModel> Persons { get; set; }
public CompanyViewModel()
{
Persons = new ObservableCollection<PersonViewModel>();
Persons.Add(new PersonViewModel(new Person { Name = "Kalle" }));
Persons.Add(new PersonViewModel(new Person { Name = "Nisse" }));
Persons.Add(new PersonViewModel(new Person { Name = "Jocke" }));
}
}
PersonViewModel.cs:
public class PersonViewModel : INotifyPropertyChanged
{
Person _person;
TestCommand _testCommand;
public PersonViewModel(Person person)
{
_person = person;
_testCommand = new TestCommand(this);
}
public ICommand ChangeCommand
{
get
{
return _testCommand;
}
}
public string Name
{
get
{
return _person.Name;
}
set
{
if (value == _person.Name)
return;
_person.Name = value;
OnPropertyChanged("Name");
}
}
void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
TestCommand.cs:
public class TestCommand : ICommand
{
PersonViewModel _person;
public event EventHandler CanExecuteChanged;
public TestCommand(PersonViewModel person)
{
_person = person;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_person.Name = "Changed by command";
}
}
Person.cs:
public class Person
{
public string Name { get; set; }
}
The key thing to remember here is context menus are not part of the visual tree.
Therefore they don't inherit the same source as the control they belong to for binding. The way to deal with this is to bind to the placement target of the ContextMenu itself.
<MenuItem Header="Change" Command="{Binding
Path=PlacementTarget.ChangeCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
/>
The cleanest way I have found to bind commands to context menu items involves using a class called CommandReference. You can find it in the MVVM toolkit on Codeplex at WPF Futures.
The XAML might look like this:
<UserControl x:Class="View.MyView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ViewModel;assembly=MyViewModel"
xmlns:mvvm="clr-namespace:ViewModelHelper;assembly=ViewModelHelper"
<UserControl.Resources>
<mvvm:CommandReference x:Key="MyCustomCommandReference" Command="{Binding MyCustomCommand}" />
<ContextMenu x:Key="ItemContextMenu">
<MenuItem Header="Plate">
<MenuItem Header="Inspect Now" Command="{StaticResource MyCustomCommandReference}"
CommandParameter="{Binding}">
</MenuItem>
</MenuItem>
</ContextMenu>
</UserControl.Resources>
MyCustomCommand is a RelayCommand on the ViewModel. In this example, the ViewModel was attached to the view's datacontext in the code-behind.
Note: this XAML was copied from a working project and simplified for illustration. There may be typos or other minor errors.
I had the same issue recently with a ContextMenu located in a ListBox. I tried to bind a command the MVVM way without any code-behind. I finally gave up and I asked a friend for his help. He found a slightly twisted but concise solution.
He is passing the ListBox in the DataContext of the ContextMenu and then find the command in the view model by accessing the DataContext of the ListBox. This is the simplest solution that I have seen so far. No custom code, no Tag, just pure XAML and MVVM.
I posted a fully working sample on Github. Here is an excerpt of the XAML.
<Window x:Class="WpfListContextMenu.MainWindow"
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"
Title="MainWindow" Height="350" Width="268">
<Grid>
<DockPanel>
<ListBox x:Name="listBox" DockPanel.Dock="Top" ItemsSource="{Binding Items}" DisplayMemberPath="Name"
SelectionMode="Extended">
<ListBox.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Show Selected" Command="{Binding Path=DataContext.ShowSelectedCommand}"
CommandParameter="{Binding Path=SelectedItems}" />
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
</DockPanel>
</Grid>
</Window>
I prefer another solution.
Add context menu loader event.
<ContextMenu Loaded="ContextMenu_Loaded">
<MenuItem Header="Change" Command="{Binding Path=ChangeCommand}"/>
</ContextMenu>
Assign data context within the event.
private void ContextMenu_Loaded(object sender, RoutedEventArgs e)
{
(sender as ContextMenu).DataContext = this; //assignment can be replaced with desired data context
}
I found this method using the Tag property very useful when binding from a context menu deep inside a control template:
http://blog.jtango.net/binding-to-a-menuitem-in-a-wpf-context-menu
This makes it possible to bind to any datacontext available to the control that the context menu was opened from. The context menu can access the clicked control through "PlacementTarget". If the Tag property of the clicked control is bound to a desired datacontext, binding to "PlacementTarget.Tag" from inside the context menu will slingshot you directly to that datacontext.
I know this is already an old post, but I would like to add another solution for those one who are looking for different ways to do it.
I could not make the same solution to work in my case, since I was trying to do something else: open the context menu with a mouse click (just like a toolbar with a submenu attached to it) and also bind commands to my model. Since I was using an Event Trigger, the PlacementTarget object was null.
This is the solution I found to make it work only using XAML:
<!-- This is an example with a button, but could be other control -->
<Button>
<...>
<!-- This opens the context menu and binds the data context to it -->
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="ContextMenu.DataContext">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{Binding}"/>
</ObjectAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="ContextMenu.IsOpen">
<DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Button.Triggers>
<!-- Here it goes the context menu -->
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Item 1" Command="{Binding MyCommand1}"/>
<MenuItem Header="Item 2" Command="{Binding MyCommand2}"/>
</ContextMenu>
</Button.ContextMenu>
</Button>