I am trying to create an attached behavior that can be applied to a Silverlight ComboBox.
My behavior is this:
using System.Windows.Controls;
using System.Windows;
using System.Windows.Controls.Primitives;
namespace AttachedBehaviours
{
public class ConfirmChangeBehaviour
{
public static bool GetConfirmChange(Selector cmb)
{
return (bool)cmb.GetValue(ConfirmChangeProperty);
}
public static void SetConfirmChange(Selector cmb, bool value)
{
cmb.SetValue(ConfirmChangeProperty, value);
}
public static readonly DependencyProperty ConfirmChangeProperty =
DependencyProperty.RegisterAttached("ConfirmChange", typeof(bool), typeof(Selector), new PropertyMetadata(true, ConfirmChangeChanged));
public static void ConfirmChangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
Selector instance = d as Selector;
if (args.NewValue is bool == false)
return;
if ((bool)args.NewValue)
instance.SelectionChanged += OnSelectorSelectionChanged;
else
instance.SelectionChanged -= OnSelectorSelectionChanged;
}
static void OnSelectorSelectionChanged(object sender, RoutedEventArgs e)
{
Selector item = e.OriginalSource as Selector;
MessageBox.Show("Unsaved changes. Are you sure you want to change teams?");
}
}
}
This is used in XAML as this:
<UserControl x:Class="AttachedBehaviours.MainPage"
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:this="clr-namespace:AttachedBehaviours"
mc:Ignorable="d">
<Grid x:Name="LayoutRoot">
<StackPanel>
<ComboBox ItemsSource="{Binding Teams}"
this:ConfirmChangeBehaviour.ConfirmChange="true" >
</ComboBox>
</StackPanel>
</Grid>
</UserControl>
I am getting an error:
Unknown attribute ConfirmChangeBehaviour.ConfirmChange on element ComboBox. [Line: 13 Position: 65]
Intellisense is picking up the behavior, why is this failing at runtime?
Thanks,
Mark
EDIT: Register() changed to RegisterAttached(). Same error appears.
You've misregistered your attached property
public static readonly DependencyProperty ConfirmChangeProperty =
DependencyProperty.RegisterAttached("ConfirmChange", typeof(bool), typeof(Selector), new PropertyMetadata(true, ConfirmChangeChanged));
Should be
public static readonly DependencyProperty ConfirmChangeProperty =
DependencyProperty.RegisterAttached("ConfirmChange", typeof(bool), typeof(ConfirmChangeBehaviour), new PropertyMetadata(true, ConfirmChangeChanged));
May I advise you to move over to using the Blend Interactivity Behaviours. Writing XAML as opposed to using a tool never makes Designers happy.
You need to change this:
DependencyProperty.Register("ConfirmChange"...
to this:
DependencyProperty.RegisterAttached("ConfirmChange"...
Attached properties (including attached behaviours) must be registered using RegisterAttached rather than plain old Register.
Related
I am trying to learn dependency properties and attached properties, so please forgive me if you find no use in what I am trying to do.
I have a usual MVVM approach with a Window whose datacontext is set to a VM, and View which is a datatemplate containing a usercontrol targetting such VM.
I am trying to make the window container as dumb as possible, as such i'm trying to define some parameters that usually reside in the window XAML (e.g. the height) via the usercontrol using Attached Properties.
For that purpose I created the following class where I define the attached property:
public static class WpfExtensions
{
public static readonly DependencyProperty ContainerHeightProperty = DependencyProperty.RegisterAttached(
"ContainerHeight",
typeof(double),
typeof(WpfExtensions),
new FrameworkPropertyMetadata(300.0, FrameworkPropertyMetadataOptions.AffectsParentArrange | FrameworkPropertyMetadataOptions.AffectsParentMeasure | FrameworkPropertyMetadataOptions.AffectsRender, ContainerHeight)
);
public static void SetContainerHeight(UIElement element, double value)
{
element.SetValue(ContainerHeightProperty, value);
}
public static double GetContainerHeight((UIElement element)
{
return (double)element.GetValue(ContainerHeightProperty);
}
private static void ContainerHeight(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is UserControl)
{
UserControl l_Control = (UserControl)d;
Binding l_Binding = new Binding();
l_Binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Window), 1);
l_Binding.Path = new PropertyPath("Height");
l_Binding.Mode = BindingMode.OneWayToSource;
BindingOperations.SetBinding(d, e.Property, l_Binding);
}
}
}
as you can see, to achieve the control of the container window height I am creating a binding in code from the attached property up to the container window.
However the ContainerHeight change get fired twice. The first time I get a change from 300 (the default) to 1024(what is defined in XAML). Then I immediately receive another one from 1024 back to 300 and I am not understanding why.
The window code is very simple:
<Window x:Class="WpfApplication.DialogWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lcl="clr-namespace:WpfApplication"
Title="DialogWindow">
<Window.Resources>
<DataTemplate DataType="{x:Type lcl:Dialog_VM}">
<lcl:Dialog_VM_View />
</DataTemplate>
</Window.Resources>
<ContentPresenter Content="{Binding }" />
</Window>
and finally the simple ViewModel
<UserControl x:Class="WpfApplication.Dialog_VM_View"
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:lcl="clr-namespace:WpfApplication"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
lcl:WpfExtensions.ContainerHeight="1024">
<StackPanel>
<TextBlock Text="This is a test" />
</StackPanel>
</UserControl>
You should bind the Height property of the parent window to the attached property and not the other way around, shouldn't you?
This sets (binds) the Height of the parent window to 1024 which is the value of the dependency property in the UserControl:
private static void ContainerHeight(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is UserControl)
{
UserControl l_Control = (UserControl)d;
if (!l_Control.IsLoaded)
{
l_Control.Loaded += L_Control_Loaded;
}
else
{
Bind(l_Control);
}
}
}
private static void L_Control_Loaded(object sender, RoutedEventArgs e)
{
UserControl l_Control = (UserControl)sender;
Bind(l_Control);
}
private static void Bind(UserControl l_Control)
{
Window window = Window.GetWindow(l_Control);
Binding l_Binding = new Binding();
l_Binding.Path = new PropertyPath(WpfExtensions.ContainerHeightProperty);
l_Binding.Source = l_Control;
BindingOperations.SetBinding(window, Window.HeightProperty, l_Binding);
}
I have very basic question regarding dependency property and data-binding. I have created a simple class name TDTVm its my ViewModel class. It has one bool dependency property named IsShaftMovingUp and its initial value is 'False' I have bound this value to one text box on UI. Now I want to show real-time value of 'IsShaftMovingUp' on the screen.
Below is my VM.
public class TDTVm : DependencyObject
{
public static DependencyProperty ShaftMovingUpProperty =
DependencyProperty.Register(
"ShaftMovingUp",
typeof(bool),
typeof(TDTVm),
new PropertyMetadata(false, ShaftMovingUpChanged));
private static void ShaftMovingUpChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Console.WriteLine("ok");
}
public bool IsShaftMovingUp
{
get => (bool)GetValue(TDTVm.ShaftMovingUpProperty);
set => SetValue(TDTVm.ShaftMovingUpProperty, value);
}
}
Below is my xamal code.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Button Content="Button" Click="Button_Click"/>
<TextBox Text="{Binding IsShaftMovingUp,
UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Window>
and below is my code behind:
public partial class MainWindow : Window
{
TDTVm datacontext = new TDTVm();
public MainWindow()
{
InitializeComponent();
this.DataContext = datacontext;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
///Even after this line 'true' value is not getting updated on UI.
datacontext.IsShaftMovingUp = true;
}
}
When I click on button I am setting value of 'IsShaftMovingUp' to true. But still on UI its not getting updated. ( I have achieved this using INotifyPropertyChanged but want to try same with dependency property to understand exact difference between the two )
Thanks
To fix your problem, you need to change this code
DependencyProperty.Register("ShaftMovingUp",
into
DependencyProperty.Register("IsShaftMovingUp",
Check this post, if you want to know the difference between INotifyPropertyChanged and Dependency Property.
Thank you so much for your help.
I'm trying to understand the ViewToViewModel attribute by getting a small example to work. I've go a couple of questions. My code is below.
Is the [ViewToViewModel] attribute supposed to be placed to be placed in the View, ViewModel or both?
If I try to use an attribute, MappingType, such as: [ViewToViewModel, MappingType = ...] MappingType gives me an error. Am I missing a "using" statement/Assembly Reference? Is there an example of syntax?
I'm able to get things to work the way I need, but I don't think that I'm getting the "ViewToViewModel" part to work properly. In the codebehind of the usercontrol, property changes are handled in HandleMyName(object e). Is ViewToViewModel supposed to do this?
Views:
MainWindow
UserControlView
ViewModels:
MainwindowViewModel
UserControlViewViewmodel
MainWindow
<catel:DataWindow x:Class="ViewToViewModelStudy.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://catel.codeplex.com"
xmlns:uc="clr-namespace:ViewToViewModelStudy.Views" >
<StackPanel x:Name="LayoutRoot">
<Label Content="{Binding Title}" />
<uc:UserControlView MyName="{Binding Title}" />
</StackPanel>
</catel:DataWindow>
.
UserControlView.xaml
<catel:UserControl x:Class="ViewToViewModelStudy.Views.UserControlView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://catel.codeplex.com">
<StackPanel>
<TextBlock>Innerview Model</TextBlock>
<TextBlock Text="{Binding MyName}"></TextBlock>
<TextBlock>Innerview Model</TextBlock>
</StackPanel>
</catel:UserControl>
UserControlView.xaml.cs
namespace ViewToViewModelStudy.Views
{
using Catel.Windows.Controls;
using Catel.MVVM.Views;
using System.Windows;
using System.Data;
public partial class UserControlView : UserControl
{
[ViewToViewModel]
public string MyName
{
get { return (string)GetValue(MyNameProperty); }
set { SetValue(MyNameProperty, value); }
}
public static readonly DependencyProperty MyNameProperty =
DependencyProperty.Register(
"MyName",
typeof(string),
typeof(UserControlView),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnMyName)));
static void OnMyName(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
UserControlView ic = (UserControlView)obj;
ic.HandleMyName(e.NewValue);
}
private void HandleMyName(object e)
{
ViewModels.UserControlViewModel vm = (ViewModels.UserControlViewModel)this.ViewModel;
if (vm != null)
{
vm.MyName = e.ToString(); // << Shouldn't this happen automagically?
}
}
public UserControlView()
{
InitializeComponent();
}
}
}
UserControlViewModel.cs
namespace ViewToViewModelStudy.ViewModels
{
using Catel.MVVM;
using Catel.Data;
using Catel.MVVM.Views;
using Catel.Windows.Controls;
public class UserControlViewModel : ViewModelBase
{
public UserControlViewModel()
{ }
public string MyName
{
get { return GetValue<string>(MyNameProperty); }
set { SetValue(MyNameProperty, value); }
}
public static readonly PropertyData MyNameProperty = RegisterProperty("MyName", typeof(string), null, (sender, e) => ((UserControlViewModel)sender).OnMyPropertyChanged());
private void OnMyPropertyChanged()
{
}
}
}
1) A ViewToViewModel should be located in the view (you don't want to pollute your VM with it).
2) The attribute should be used as [ViewToViewModel(MappingType = ...)]
3) The ViewToViewModel should handle the automatic mapping of property x on the view to property x on the view model. It will handle all change notifications automatically.
I have two UserControls (uc1 and uc2) loading into a third UserControl (shell). Shell has two properties, uc1 and uc2, of type UserControl1 and UserControl2, and each have a DependencyProperty registered to their own classes called IsDirty:
public static readonly DependencyProperty IsDirtyProperty = DependencyProperty.Register("IsDirty", typeof (bool), typeof (UserControl1));
public bool IsDirty
{
get { return (bool) GetValue(IsDirtyProperty); }
set { SetValue(IsDirtyProperty, value); }
}
(same code for UserControl2)
Shell has TextBlocks bound to the IsDirty properties:
<TextBlock Text="{Binding ElementName=shell, Path=Uc1.IsDirty}"/>
<TextBlock Text="{Binding ElementName=shell, Path=Uc2.IsDirty}"/>
When I change the values of IsDirty in uc1 and uc2, Shell never gets notified. What am I missing? UserControl is descendant of DependencyObject...
The same behavior occurs if I have regular properties notifying changes via INotifyPropertyChanged.
If I raise a routed event from uc1 and uc2, bubbling up to Shell, then I can catch the Dirty value and everything works, but I shouldn't have to do that, should I?
Thanks
Edit: The answer is to raise property changed event on the Uc1 and Uc2 properties or make them DPs.
I tried reproducing your problem using a simple setup, and it works fine for me. I'm not sure though if this setup is correct enough to replicate your situation. Anyway, I'm posting it just in case. It might be helpful:
XAML:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
x:Name="shell"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Button Click="Button_Click">Click</Button>
<TextBlock Text="{Binding ElementName=shell, Path=Uc1.IsDirty}"/>
</StackPanel>
</Window>
Code-Behind:
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MyUserControl uc1 = new MyUserControl();
public MyUserControl Uc1
{
get { return this.uc1; }
}
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.uc1.IsDirty = !this.uc1.IsDirty;
}
}
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
}
public bool IsDirty
{
get { return (bool)GetValue(IsDirtyProperty); }
set { SetValue(IsDirtyProperty, value); }
}
// Using a DependencyProperty as the backing store for IsDirty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsDirtyProperty =
DependencyProperty.Register("IsDirty", typeof(bool), typeof(UserControl), new UIPropertyMetadata(false));
}
}
Karmicpuppet's answer works well. However it didn't solve my problem because Shell is also of type UserControl. For it to work I needed to raise the property changed on Uc1 and Uc2. When I declared them as DependencyProperties all worked as expected. Duh!
I want to create a Style for a WPF ListBox that includes a Button in the ControlTemplate that the user can click on and it clears the ListBox selection.
I dont want to use codebehind so that this Style can be applied to any ListBox.
I have tried using EventTriggers and Storyboards and it has proved problematic as it only works first time and stopping the Storyboard sets the previous selection back.
I know I could use a user control but I want to know if it is possible to achieve this using only a Style.
It is not possible to achieve this using XAML and only the classes provided by the .NET framework. However you can still produce a reusable solution by defining a new command (call it ClearSelectionCommand) and a new attached property (call it ClearSelectionOnCommand).
Then you can incorporate those elements into your style.
Example:
public class SelectorBehavior
{
public static RoutedCommand
ClearSelectionCommand =
new RoutedCommand(
"ClearSelectionCommand",
typeof(SelectorBehavior));
public static bool GetClearSelectionOnCommand(DependencyObject obj)
{
return (bool)obj.GetValue(ClearSelectionOnCommandProperty);
}
public static void SetClearSelectionOnCommand(
DependencyObject obj,
bool value)
{
obj.SetValue(ClearSelectionOnCommandProperty, value);
}
public static readonly DependencyProperty ClearSelectionOnCommandProperty =
DependencyProperty.RegisterAttached(
"ClearSelectionOnCommand",
typeof(bool),
typeof(SelectorBehavior),
new UIPropertyMetadata(false, OnClearSelectionOnCommandChanged));
public static void OnClearSelectionOnCommandChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
Selector selector = d as Selector;
if (selector == null) return;
bool nv = (bool)e.NewValue, ov = (bool)e.OldValue;
if (nv == ov) return;
if (nv)
{
selector.CommandBindings.Add(
new CommandBinding(
ClearSelectionCommand,
ClearSelectionCommand_Executed,
ClearSelectionCommand_CanExecute));
}
else
{
var cmd = selector
.CommandBindings
.Cast<CommandBinding>()
.SingleOrDefault(x =>
x.Command == ClearSelectionCommand);
if (cmd != null)
selector.CommandBindings.Remove(cmd);
}
}
public static void ClearSelectionCommand_Executed(
object sender,
ExecutedRoutedEventArgs e)
{
Selector selector = (Selector)sender;
selector.SelectedIndex = -1;
}
public static void ClearSelectionCommand_CanExecute(
object sender,
CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
}
Example usage - XAML:
<Window x:Class="ClearSelectionBehaviorLibrary.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ClearSelectionBehaviorLibrary"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<Style x:Key="MyStyle" TargetType="Selector">
<Setter
Property="local:SelectorBehavior.ClearSelectionOnCommand"
Value="True"/>
</Style>
</Window.Resources>
<Grid>
<DockPanel>
<Button
DockPanel.Dock="Bottom"
Content="Clear"
Command="{x:Static local:SelectorBehavior.ClearSelectionCommand}"
CommandTarget="{Binding ElementName=TheListBox}"/>
<ListBox
Name="TheListBox"
ItemsSource="{Binding MyData}"
Style="{StaticResource MyStyle}"/>
</DockPanel>
</Grid>
</Window>
Example usage - Code Behind:
public partial class Window1 : Window
{
public List<string> MyData { get; set; }
public Window1()
{
MyData = new List<string>
{
"aa","bb","cc","dd","ee"
};
InitializeComponent();
DataContext = this;
}
}