As the SelectedItems property of the ListBox control is a normal property and not a dependency property to bind to, I have derived of the ListBox and created a new dependency property SelectedItemsEx.
But my XAML compiler keeps giving me the error
A 'Binding' cannot be set on the 'SelectedItemsEx' property of the
type 'MyListBox'. A 'Binding' can only be set on a DependencyProperty
of a DependencyObject.
Why my property is not recognized as a dependency property? Any help is appreciated, thank you!
XAML:
<MyListBox ItemsSource="{Binding MyData}" SelectedItemsEx="{Binding SelectedEx}"
SelectionMode="Extended"> ... </MyListBox>
ListBox' implementation:
public class MyListBox : ListBox
{
public readonly DependencyProperty SelectedItemsExProperty =
DependencyProperty.Register("SelectedItemsEx",
typeof(ObservableCollection<MyItemsDataType>),
typeof(MyListBox),
new PropertyMetadata(default(ObservableCollection<MyItemsDataType>)));
public ObservableCollection<MyItemsDataType> SelectedItemsEx
{
get
{
var v = GetValue(SelectedItemsExProperty);
return (ObservableCollection<MyItemsDataType>)v;
}
set { SetValue(SelectedItemsExProperty, value); }
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
if (SelectedItemsEx != null)
{
SelectedItemsEx.Clear();
foreach (var item in base.SelectedItems)
{
SelectedItemsEx.Add((MyItemsDataType)item);
}
}
}
The DependencyProperty field must be static:
public static readonly DependencyProperty SelectedItemsExProperty = ...
Note also that in order to make your derived ListBox a little more reusable, you should not constrain the type of the SelectedItemsEx property. Use IEnumerable (or IList like SelectedItems) instead. Moreover, there is no need to specify a default value by property metadata, as it is null already and default(<any reference type>) is also null.
You will however have to get notified whenever the SelectedItemsEx property has changed. Therefore you have to register a change callback via property metadata:
public static readonly DependencyProperty SelectedItemsExProperty =
DependencyProperty.Register(
"SelectedItemsEx", typeof(IEnumerable), typeof(MyListBox),
new PropertyMetadata(SelectedItemsExPropertyChanged));
public IEnumerable SelectedItemsEx
{
get { return (IEnumerable)GetValue(SelectedItemsExProperty); }
set { SetValue(SelectedItemsExProperty, value); }
}
private static void SelectedItemsExPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var listBox = (MyListBox)obj;
var oldColl = e.OldValue as INotifyCollectionChanged;
var newColl = e.NewValue as INotifyCollectionChanged;
if (oldColl != null)
{
oldColl.CollectionChanged -= listBox.SelectedItemsExCollectionChanged;
}
if (newColl != null)
{
newColl.CollectionChanged += listBox.SelectedItemsExCollectionChanged;
}
}
private void SelectedItemsExCollectionChanged(
object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
...
}
}
Related
I have a dependency property in a user control
public List<Exclusion> SelectedExclusions
{
get { return (List<Exclusion>)GetValue(SelectedExclusionsProperty); }
set { SetValue(SelectedExclusionsProperty, value); }
}
public static readonly DependencyProperty SelectedExclusionsProperty =
DependencyProperty.Register(nameof(TimeSeriesChart.SelectedExclusions), typeof(List<Exclusion>), typeof(TimeSeriesChart), new PropertyMetadata(new List<Exclusion>()));
The list is populated on delete key down:
protected override void OnKeyDown(KeyEventArgs e)
{
if(e.Key == Key.Delete)
{
this.SelectedExclusions.Add(this.Exclusions[this.Index]);
}
}
}
In the view model I new up a list & public property. A delete command is invoked from the setter:
private IList<Exclusion> selectedExclusionsToDelete = new List<Exclusion>();
public IList<Exclusion> SelectedExclusionsToDelete
{
get
{
return this.selectedExclusionsToDelete;
}
set
{
this.selectedExclusionsToDelete = value;
//Delete the selected exclusion
ExecuteDeleteSelectedExclusionsCommand();
this.RaisePropertyChanged();
}
}
Finally, my xaml binding in the view:
SelectedExclusions="{Binding SelectedExclusionsToDelete, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
My problem is that at run time the SelectedExclusions dp getter returns null instead of a List<Exclusion> Is there something I'm missing here given that the PropertyMetadata value is of type List<Exclusion>?
I have a written a control but I am having some issues binding a CollectionViewSource to my ItemsSource property. If I Bind an ObservableCollection to the ItemsSource property everything works as expected. With the CollectionViewSource bound to it I get the following binding error in the output.
Error: Converter failed to convert value of type 'Windows.UI.Xaml.Data.ICollectionView' to type 'IBindableIterable'; BindingExpression: Path='JobView.View' DataItem='App.ViewModel.MainViewModel'; target element is MySpecialControl.MySpecialControl' (Name='null'); target property is 'ItemsSource' (type 'IBindableIterable').
public sealed class MySpecialControl: Control
{
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(MySpecialControl), new PropertyMetadata((IEnumerable)null, OnItemSourcePropertyChanged));
private static void OnItemSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MySpecialControl)sender).OnItemSourcePropertyChanged((IEnumerable) eventAgrs.OldValue, (IEnumerable)eventAgrs.NewValue);
}
private void OnItemSourcePropertyChanged(IEnumerable oldValue, IEnumerable newValue)
{
INotifyCollectionChanged oldCollectionChanged = oldValue as INotifyCollectionChanged;
if (oldCollectionChanged != null)
oldCollectionChanged.CollectionChanged -= ItemSource_CollectionChanged;
INotifyCollectionChanged newCollectionChanged = newValue as INotifyCollectionChanged;
if (newCollectionChanged != null)
newCollectionChanged.CollectionChanged += ItemSource_CollectionChanged;
}
private void ItemSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
...
}
}
In my XAML I have bound it to the CollectionViewSource.View
Any ideas how to change the ItemsSource to accept an ObservableCollection as well as a CollectionViewSource.View ?
Thanks
I have created a custom TextEditor control that inherits from AvalonEdit. I have done this to facilitate the use of MVVM and Caliburn Micro using this editor control. The [cut down for display purposes] MvvTextEditor class is
public class MvvmTextEditor : TextEditor, INotifyPropertyChanged
{
public MvvmTextEditor()
{
TextArea.SelectionChanged += TextArea_SelectionChanged;
}
void TextArea_SelectionChanged(object sender, EventArgs e)
{
this.SelectionStart = SelectionStart;
this.SelectionLength = SelectionLength;
}
public static readonly DependencyProperty SelectionLengthProperty =
DependencyProperty.Register("SelectionLength", typeof(int), typeof(MvvmTextEditor),
new PropertyMetadata((obj, args) =>
{
MvvmTextEditor target = (MvvmTextEditor)obj;
target.SelectionLength = (int)args.NewValue;
}));
public new int SelectionLength
{
get { return base.SelectionLength; }
set { SetValue(SelectionLengthProperty, value); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] string caller = null)
{
var handler = PropertyChanged;
if (handler != null)
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
}
Now, in the view that holds this control, I have the following XAML:
<Controls:MvvmTextEditor
Caliburn:Message.Attach="[Event TextChanged] = [Action DocumentChanged()]"
TextLocation="{Binding TextLocation, Mode=TwoWay}"
SyntaxHighlighting="{Binding HighlightingDefinition}"
SelectionLength="{Binding SelectionLength,
Mode=TwoWay,
NotifyOnSourceUpdated=True,
NotifyOnTargetUpdated=True}"
Document="{Binding Document, Mode=TwoWay}"/>
My issue is SelectionLength (and SelectionStart but let us just consider the length for now as the problem is the same). If I selected something with the mouse, the binding from the View to my View Model works great. Now, I have written a find and replace utility and I want to set the SelectionLength (which has get and set available in the TextEditor control) from the code behind. In my View Model I am simply setting SelectionLength = 50, I implement this in the View Model like
private int selectionLength;
public int SelectionLength
{
get { return selectionLength; }
set
{
if (selectionLength == value)
return;
selectionLength = value;
Console.WriteLine(String.Format("Selection Length = {0}", selectionLength));
NotifyOfPropertyChange(() => SelectionLength);
}
}
when I set SelectionLength = 50, the DependencyProperty SelectionLengthProperty does not get updated in the MvvmTextEditor class, it is like the TwoWay binding to my control is failing but using Snoop there is no sign of this. I thought this would just work via the binding, but this does not seem to be the case.
Is there something simple I am missing, or will I have to set up and event handler in the MvvmTextEditor class which listens for changes in my View Model and updated the DP itself [which presents it's own problems]?
Thanks for your time.
This is because the Getter and Setter from a DependencyProperty is only a .NET Wrapper. The Framework will use the GetValue and SetValue itself.
What you can try is to access the PropertyChangedCallback from your DependencyProperty and there set the correct Value.
public int SelectionLength
{
get { return (int)GetValue(SelectionLengthProperty); }
set { SetValue(SelectionLengthProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectionLength. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectionLengthProperty =
DependencyProperty.Register("SelectionLength", typeof(int), typeof(MvvmTextEditor), new PropertyMetadata(0,SelectionLengthPropertyChanged));
private static void SelectionLengthPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var textEditor = obj as MvvmTextEditor;
textEditor.SelectionLength = e.NewValue;
}
Here is another answer if you are still open. Since SelectionLength is already defined as a dependency property on the base class, rather than create a derived class (or add an already existing property to the derived class), I would use an attached property to achieve the same functionality.
The key is to use System.ComponentModel.DependencyPropertyDescriptor to subscribe to the change event of the already existing SelectionLength dependency property and then take your desired action in the event handler.
Sample code below:
public class SomeBehavior
{
public static readonly DependencyProperty IsEnabledProperty
= DependencyProperty.RegisterAttached("IsEnabled",
typeof(bool), typeof(SomeBehavior), new PropertyMetadata(OnIsEnabledChanged));
public static void SetIsEnabled(DependencyObject dpo, bool value)
{
dpo.SetValue(IsEnabledProperty, value);
}
public static bool GetIsEnabled(DependencyObject dpo)
{
return (bool)dpo.GetValue(IsEnabledProperty);
}
private static void OnIsEnabledChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs args)
{
var editor = dpo as TextEditor;
if (editor == null)
return;
var dpDescriptor = System.ComponentModel.DependencyPropertyDescriptor.FromProperty(TextEditor.SelectionLengthProperty,editor.GetType());
dpDescriptor.AddValueChanged(editor, OnSelectionLengthChanged);
}
private static void OnSelectionLengthChanged(object sender, EventArgs e)
{
var editor = (TextEditor)sender;
editor.Select(editor.SelectionStart, editor.SelectionLength);
}
}
Xaml below:
<Controls:TextEditor Behaviors:SomeBehavior.IsEnabled="True">
</Controls:TextEditor>
This is how I did this...
public static readonly DependencyProperty SelectionLengthProperty =
DependencyProperty.Register("SelectionLength", typeof(int), typeof(MvvmTextEditor),
new PropertyMetadata((obj, args) =>
{
MvvmTextEditor target = (MvvmTextEditor)obj;
if (target.SelectionLength != (int)args.NewValue)
{
target.SelectionLength = (int)args.NewValue;
target.Select(target.SelectionStart, (int)args.NewValue);
}
}));
public new int SelectionLength
{
get { return base.SelectionLength; }
//get { return (int)GetValue(SelectionLengthProperty); }
set { SetValue(SelectionLengthProperty, value); }
}
Sorry for any time wasted. I hope this helps someone else...
In the following code snippet:
<ItemsControl helpers:EnumHelper.Enum="order:TShirtSizeEnum" ... >
...
</ItemsControl>
what helpers:EnumHelper.Enum attribute means?
Next is implementation of EnumHelper class
public class EnumHelper : DependencyObject
{
public static Type GetEnum(DependencyObject obj)
{
return (Type)obj.GetValue(EnumProperty);
}
public static void SetEnum(DependencyObject obj, string value)
{
obj.SetValue(EnumProperty, value);
}
// Using a DependencyProperty as the backing store for Enum. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EnumProperty =
DependencyProperty.RegisterAttached("Enum", typeof(Type), typeof(EnumHelper), new PropertyMetadata(null, OnEnumChanged));
private static void OnEnumChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as ItemsControl;
if (control != null)
{
if (e.NewValue != null && e.NewValue as Type != null)
{
var _enum = Enum.GetValues(e.NewValue as Type);
control.ItemsSource = _enum;
}
}
}
}
Presumably an attached property. What it does depends on what helpers refers to (as it's probably not part of .NET i cannot tell you anything about it, but i would guess it uses reflection to get the enum values and sets them as ItemsSource).
Looks like it means the following: find class EnumHelper in namespace referenced above as helpers. This class defines an attached property Enum. Set the value of attached property for this object to ...
i m inheriting ListBox to my class and i want to override itemsource property.
i actually want to do someoperation when itemsource is assigned.
how it is possible?
i want in c# code only not in xaml
The way you override a dependency property in WPF is this way....
public class MyListBox : ListBox
{
//// Static constructor to override.
static MyListBox()
{
ListBox.ItemsSourceProperty.OverrideMetadata(typeof(MyListBox), new FrameworkPropertyMetadata(null, MyListBoxItemsSourceChanged));
}
private static void MyListBoxItemsSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var myListBox = sender as MyListBox;
//// You custom code.
}
}
Is this what you are looking for?
Why not just set SourceUpdated event handler before setting the item source property?
For Example if MyListBox is your Listbox & MyItemSource is the source, you may set event handler n call it as follows:
void MyFunction()
{
MyListBox.SourceUpdated += new EventHandler<DataTransferEventArgs>(MyListBox_SourceUpdated);
MyListBox.ItemsSource = MyItemSource;
}
void MyListBox_SourceUpdated(object sender, DataTransferEventArgs e)
{
// Do your work here
}
Also, make sure that your data source implements INotifyPropertyChanged or INotifyCollectionChanged events.
Here i have crated a Custom List box which extends from Listbox and it got a dependency property itemssource...
When ever item source got updated you can do your operations and after that you can call the updatesourcemethod of customlistbox which will assign the itemsSource property of BaseClass.
public class CustomListBox : ListBox
{
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
// Using a DependencyProperty as the backing store for ItemsSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(CustomListBox), new UIPropertyMetadata(0, ItemsSourceUpdated));
private static void ItemsSourceUpdated(object sender, DependencyPropertyChangedEventArgs e)
{
var customListbox = (sender as CustomListBox);
// Your Code
customListbox.UpdateItemssSource(e.NewValue as IEnumerable);
}
protected void UpdateItemssSource(IEnumerable source)
{
base.ItemsSource = source;
}
}