I created an attached property for ListBox like this:
using ListBoxControl = System.Windows.Controls.ListBox;
namespace App.Ui.Views.AttachedProperties
{
public class ListBox
{
public static readonly DependencyProperty autoScrollProperty =
DependencyProperty.RegisterAttached(
"AutoScroll",
typeof(bool),
typeof(ListBoxControl),
new PropertyMetadata(false));
public static void SetAutoScroll(ListBoxControl element, bool value)
{
element.SetValue(autoScrollProperty, value);
if (value)
{
element.SelectionChanged += Element_SelectionChanged;
}
else
{
element.SelectionChanged -= Element_SelectionChanged;
}
}
public static bool GetAutoScroll(ListBoxControl element)
{
return (bool)element.GetValue(autoScrollProperty);
}
private static void Element_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listBox = (ListBoxControl)sender;
listBox.ScrollIntoView(listBox.SelectedItem);
}
}
}
When I use a static True/False value in the xaml, it works fine:
<ListBox ap:ListBox.AutoScroll="True">
...
</ListBox>
But if I data bind to a property in my view model:
<ListBox ap:ListBox.AutoScroll="{Binding Path=Settings.EnableAutoScroll}">
...
</ListBox>
Then I get the following exception: A 'Binding' cannot be set on the 'SetAutoScroll' property of type 'ListBox'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
Is this possible, or am I going to need to derive my own custom list box to accomplish this?
Problem in this line typeof(ListBoxControl). You should specify the name of the class where custom attached property seats.
I would recommend rename class from ListBox to ListBoxExtensions, also, make it static. Then you don't have to use alias ListBoxControl.
Your final code will look like:
public static class ListBoxExtensions
{
public static readonly DependencyProperty autoScrollProperty =
DependencyProperty.RegisterAttached(
"AutoScroll",
typeof(bool),
typeof(ListBoxExtensions),
new PropertyMetadata(false));
...
}
Edit:
OK, your code has another problem.
Remove attachment of the listener from setter (SetAutoScroll) and put this logic into dependency property callback.
public static class ListBoxExtensions
{
public static readonly DependencyProperty autoScrollProperty =
DependencyProperty.RegisterAttached(
"AutoScroll",
typeof(bool),
typeof(ListBoxExtensions),
new PropertyMetadata(false, AutoScrollChangedCallback));
public static void SetAutoScroll(ListBox element, bool value)
{
element.SetValue(autoScrollProperty, value);
}
public static bool GetAutoScroll(ListBox element)
{
return (bool)element.GetValue(autoScrollProperty);
}
private static void AutoScrollChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ListBox control = (ListBox)d;
if ((bool)e.NewValue)
{
control.SelectionChanged += Element_SelectionChanged;
}
else
{
control.SelectionChanged -= Element_SelectionChanged;
}
}
private static void Element_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listBox = (ListBox)sender;
listBox.ScrollIntoView(listBox.SelectedItem);
}
}
Related
I have a user control and within I defined the following property:
public string MyProperty
{
get { return (string)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(string),
typeof(MyUserControlView), new PropertyMetadata(null, MyPropertyChangedCallback));
private static void MyPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (MyUserControl) d;
control.MyProperty = (string)e.NewValue;
}
and the following command:
public ICommand MyCommand
{
get { return (ICommand)GetValue(MyCommandProperty); }
set { SetValue(MyCommandProperty, value); }
}
public static readonly DependencyProperty MyCommandProperty =
DependencyProperty.Register("MyCommand", typeof(ICommand), typeof(MyUserControlView),
new PropertyMetadata(null, MyCommandPropertyChangedCallback));
private static void MyCommandPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (MyUserControlView)d;
control.MyCommand = (DelegateCommand)e.NewValue;
}
void ExecuteMyCommand() {...}
bool CanExecuteMyCommand(){
return !string.IsNullOrWhiteSpace(MyProperty);
}
I Initialize this command with
SetValue(MyCommandProperty, new DelegateCommand(ExecuteMyCommand, CanExecuteMyCommand));
The problem is that the CanExecuteMyCommand() method does not work, because it does not use the current value of MyProperty. Why is this and how can I use my command correctly in a user control?
You have to invoke CanExecuteChanged after changing MyProperty it will call CanExecute.
Hello I have issue with binding to user control animation, after I bind data to user control(which is bool type) it sets correct values to user control data, but does not trigger animation, I tried to use PropertyChangedCallback but with no luck user control code below:
private static Switch_box AppWindow;
public Switch_box()
{
InitializeComponent();
AppWindow = this;
}
public static readonly DependencyProperty CheckboxStatusProperty = DependencyProperty.Register(nameof(CheckboxStatus), typeof(bool), typeof(Switch_box), new PropertyMetadata(false, new PropertyChangedCallback(OnCurrentReadingChanged)));//cant remove static otherwise throws error
public bool CheckboxStatus
{
get
{
return (bool)GetValue(CheckboxStatusProperty);
}
set
{
/* if (value == true)
{
((Storyboard)FindResource("OnChecking")).Begin(this);
}
else
{
((Storyboard)FindResource("OnUnchecking")).Begin(this);
}*/
SetValue(CheckboxStatusProperty, value);
}
}
private static void OnCurrentReadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)//cant remove static due to PropertyChangedCallBack requires static otherwise it throws error
{
AppWindow.OnChecking((bool)d.GetValue(CheckboxStatusProperty));
}
private void OnChecking(bool Status)
{
switch (Status)
{
case true:
{
((Storyboard)FindResource("OnChecking")).Begin(this);
break;
}
case false:
{
((Storyboard)FindResource("OnUnchecking")).Begin(this);
break;
}
}
}
And my usercontrol bind line:
<local:Switch_box Tag="{Binding Index,IsAsync=True}" Checked="Switch_box_Checked" Unchecked="Switch_box_Unchecked" CheckboxStatus="{Binding IsEnabled,IsAsync=True}"/>
How to trigger animation after CheckboxStatus variable is changed?
EDIT 1: updated code.
There is a naming convention. _StatusBox should be named CheckboxStatusProperty, and it should be public:
public static readonly DependencyProperty CheckboxStatusProperty =
DependencyProperty.Register(
nameof(CheckboxStatus), typeof(bool), typeof(Switch_box),
new PropertyMetadata(false, OnCurrentReadingChanged));
You must not call anything else than GetValueand SetValue in the CLR wrapper of a dependency property. And you call the methods on the current instance, not on a static field:
public bool CheckboxStatus
{
get { return (bool)GetValue(CheckboxStatusProperty); }
set { SetValue(CheckboxStatusProperty , value); }
}
In the PropertyChangedCallback it is pointless to set the property another time. And again, you should operate on the current DependencyObject instance, i.e. d, not on a static field:
private static void OnCurrentReadingChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((Switch_box)d).OnChecking((bool)e.NewValue);
}
private void OnChecking(bool status)
{
if (status)
{
((Storyboard)FindResource("OnChecking")).Begin(this);
}
else
{
((Storyboard)FindResource("OnUnchecking")).Begin(this);
}
}
I'm having really hard time trying to make listbox multiselection bindable, basically the same as this article : http://blogs.microsoft.co.il/miziel/2014/05/02/wpf-binding-listbox-selecteditems-attached-property-vs-style/
This is my class for the listbox :
public partial class ListBoxMultipleSelection : UserControl
{
public ListBoxMultipleSelection()
{
InitializeComponent();
this.DataContext = this;
}
public static readonly DependencyProperty ItemSourceProperty =
DependencyProperty.Register("ItemSource", typeof(ObservableCollection<string>), typeof(ListBoxMultipleSelection), new PropertyMetadata(new ObservableCollection<string>()));
public ObservableCollection<string> ItemSource
{
get { return (ObservableCollection<string>)this.GetValue(ItemSourceProperty); }
set { this.SetValue(ItemSourceProperty, value); }
}
private static ListBox list;
private static bool _isRegisteredSelectionChanged = false;
///
/// SelectedItems Attached Dependency Property
///
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.RegisterAttached("SelectedItems", typeof(IList),
typeof(ListBoxMultipleSelection),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(OnSelectedItemsChanged)));
public static IList GetSelectedItems(DependencyObject d)
{
return (IList)d.GetValue(SelectedItemsProperty);
}
public static void SetSelectedItems(DependencyObject d, IList value)
{
d.SetValue(SelectedItemsProperty, value);
}
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!_isRegisteredSelectionChanged)
{
var listBox = (ListBox)d;
list = listBox;
listBox.SelectionChanged += listBox_SelectionChanged;
_isRegisteredSelectionChanged = true;
}
}
private static void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
IEnumerable listBoxSelectedItems = list.SelectedItems;
IList ModelSelectedItems = GetSelectedItems(list);
ModelSelectedItems.Clear();
if (list.SelectedItems != null)
{
foreach (var item in list.SelectedItems)
ModelSelectedItems.Add(item);
}
SetSelectedItems(list, ModelSelectedItems);
}
}
And the XAML for the usercontrol/listbox :
<Grid>
<ListBox SelectionMode="Multiple"
local:ListBoxMultipleSelection.SelectedItems="{Binding DataContext.SelectedItems}"
ItemsSource="{Binding DataContext.ItemSource, ElementName=MultiListBox, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
The problem is "listBox_SelectionChanged" is never fired and so nothing is updated correctly. Any idea ?
Thank you
I am using the free version of the DevExpress Silverlight Menu (AgMenu 8.4). Sadly the MenuItems of this menu have no "Command" and "CommandParameter" properties.
I decided to inherit from the MenuItem class and implement two DependencyProperties, "Command" and "CommandProperty".
The code for this looks like this:
public partial class MenuItem : DevExpress.AgMenu.AgMenuItem
{
public MenuItem()
{
InitializeComponent();
}
private Object _CommandParameter = null;
public Object CommandParameter
{
get { return _CommandParameter; }
set { _CommandParameter = value; } //This one is triggered. This is ok.
}
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(Object), typeof(Gui.CustomControls.MenuItem), new PropertyMetadata(OnCommandParameterChanged));
private static void OnCommandParameterChanged(object sender, DependencyPropertyChangedEventArgs args)
{
//CommandParameter Object is arriving here. That is ok.
}
private ICommand _Command = null;
public ICommand Command
{
get { return _Command; }
set
{
//HERE is the problem.
//This one is NOT triggered. I dont' know why....?
_Command = value;
}
}
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(Gui.CustomControls.MenuItem), new PropertyMetadata(OnCommandChanged));
private static void OnCommandChanged(object sender, DependencyPropertyChangedEventArgs args)
{
//ICommand Object is arriving here. That is also ok.
//I don't understand, why the ICommand Object is not arriving in the set value prop
}
}
Now I am using this two DPs in my XAML. This looks like this for one MenuItem:
<cc:MenuItem x:Name ="_mnuItemLogout"
DataContext ="{Binding Source={StaticResource ViewModel}}"
Header ="{Binding Source={StaticResource MenuProvider}, Path=GetSingleton.LogoutText, Mode=OneWay}"
IsEnabled ="{Binding Source={StaticResource MenuProvider}, Path=GetSingleton.LogoutEnabled, Mode=OneWay}"
Command ="{Binding Source={StaticResource ViewModel}, Path=Command_FormOpen}"
CommandParameter ="{gui:FormOpen e=Login}"
IsCheckable ="False"
>
</cc:MenuItem>
When I am testing my silverlight application, I assume that both, the "Command" and "CommandParameter" set value properties are called, and the values are set to _Command and _CommandParameter, but only the CommandParameter set value is called.
Strangely, both static procedures "OnCommandChanged" and "OnCommandParameterChanged" are called. While debugging, I can see, both expected objects (ICommand and CommandParameter) are arriving in this two procedures.
So my question is:
What am I doing wrong, that the ICommand Object is not set in the "Set ICommand" property?
Thank you.
This case is solved. What I needed to do, is not using DependencyProperties, but attached DependencyProperties.
The MenuItem of the DevExpress Silverlight menu now accepts Command and CommandParameter objects. The Command is triggered, when the LeftMouseButtonUp event is fired. The following is the working code. The XAML stays the same (see above). You just need to make a silverlight user control and inherit from DevExpress.AgMenu.AgMenuItem. You then use this as MenuItem, instead of the original.
using System;
using System.Windows;
using System.Windows.Input;
namespace Gui.CustomControls
{
public partial class MenuItem : DevExpress.AgMenu.AgMenuItem
{
public MenuItem()
{
InitializeComponent();
}
#region CommandParameter DependencyProperty
public static Object GetCommandParameter(DependencyObject obj)
{
return (Object)obj.GetValue(CommandParameterProperty);
}
public static void SetCommandParameter(DependencyObject obj, Object value)
{
obj.SetValue(CommandParameterProperty, value);
}
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached("CommandParameter", typeof(Object), typeof(Gui.CustomControls.MenuItem), new PropertyMetadata(OnCommandParameterChanged) );
private static void OnCommandParameterChanged(object sender, DependencyPropertyChangedEventArgs args)
{
DependencyObject _DependencyObject = (DependencyObject)sender;
if ((args.NewValue != null) && (_DependencyObject != null))
{
MenuItem.SetCommandParameter(_DependencyObject, args.NewValue);
}
}
#endregion
#region Command
private static void OnCommandChanged(object sender, DependencyPropertyChangedEventArgs args)
{
DependencyObject _DependencyObject = (DependencyObject)sender;
ICommand _ICommand = (ICommand)args.NewValue;
if ((_ICommand != null) && (_DependencyObject != null))
{
SetCommand(_DependencyObject, _ICommand);
}
}
public static ICommand GetCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(CommandProperty);
}
public static void SetCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(CommandProperty, value);
}
// Using a DependencyProperty as the backing store for Command. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(Gui.CustomControls.MenuItem), new PropertyMetadata(OnCommandChanged));
#endregion
#region LeftMouseButtonUp (Command Trigger)
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonUp(e);
ICommand _ICommand = MenuItem.GetCommand(this);
Object _CommandParameter = MenuItem.GetCommandParameter(this);
if (_ICommand != null)
{
_ICommand.Execute(_CommandParameter);
}
}
#endregion
}
}
I have a custom control that has a DependencyProperty of type ObservableCollection that is bound to an observableCollection:
<MyControl MyCollectionProperty = {Binding MyObservableCollection} ...
Problem is adding to MyObservableCollection does not update MyCollectionProperty.
I need to completly replace the MyObservableCollection to make it work e.g.
MyObservableCollection = null;
MyObservableCollection = new ObservableCollection(){...}
Is there a better way to deal with this?
EDIT:
public ObservableCollection<string> Columns
{
get { return (ObservableCollection<string>)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("Columns", typeof(ObservableCollection<string>), typeof(MyControl),
new PropertyMetadata(new ObservableCollection<string>(), OnChanged));
In addition to what grantz has answered, I would suggest to declare the property with type IEnumerable<string> and check at runtime if the collection object implements the INotifyCollectionChanged interface. This provides greater flexibility as to which concrete collection implementation may be used as property value. A user may then decide to have their own specialized implementation of an observable collection.
Note also that in the ColumnsPropertyChanged callback the CollectionChanged event handler is attached to the new collection, but also removed from the old one.
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register(
"Columns", typeof(IEnumerable<string>), typeof(MyControl),
new PropertyMetadata(null, ColumnsPropertyChanged));
public IEnumerable<string> Columns
{
get { return (IEnumerable<string>)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
private static void ColumnsPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var control= (MyControl)obj;
var oldCollection = e.OldValue as INotifyCollectionChanged;
var newCollection = e.NewValue as INotifyCollectionChanged;
if (oldCollection != null)
{
oldCollection.CollectionChanged -= control.ColumnsCollectionChanged;
}
if (newCollection != null)
{
newCollection.CollectionChanged += control.ColumnsCollectionChanged;
}
control.UpdateColumns();
}
private void ColumnsCollectionChanged(
object sender, NotifyCollectionChangedEventArgs e)
{
// optionally take e.Action into account
UpdateColumns();
}
private void UpdateColumns()
{
...
}
Below is a working example that may help.
In this example, the method OnChanged is called immediately, when the Add button is clicked "Changed" is written to the console.
The Control
public class MyControl : Control
{
public ObservableCollection<string> ExtraColumns
{
get { return (ObservableCollection<string>)GetValue(ExtraColumnsProperty); }
set { SetValue(ExtraColumnsProperty, value); }
}
public static readonly DependencyProperty ExtraColumnsProperty =
DependencyProperty.Register("ExtraColumns", typeof(ObservableCollection<string>), typeof(MyControl),
new PropertyMetadata(new ObservableCollection<string>(), OnChanged));
static void OnChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
(sender as MyControl).OnChanged();
}
void OnChanged()
{
if ( ExtraColumns != null )
ExtraColumns.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ExtraColumns_CollectionChanged);
}
void ExtraColumns_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Console.WriteLine("Changed");
}
}
The Window
<Window x:Class="WpfApplication18.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication18"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<local:MyControl ExtraColumns="{Binding Extras}"/>
<Button Click="Button_Click">Add</Button>
</StackPanel>
</Window>
Window Code Behind
public partial class MainWindow : Window
{
private ObservableCollection<string> _extras = new ObservableCollection<string>( );
public ObservableCollection<string> Extras
{
get { return _extras; }
set
{
if (value != _extras)
{
_extras = value;
}
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Extras.Add("Additional");
}
}