My question concerns Silverlight (but I guess WPF as well).
Basically I know, how to create dependency property in a user control and how to make it work. But what i was trying to do, and didn't succeded is: to create dependency property (or more than one) in a class, and this class will become a dependency property for my user control.
With other words:
// my UserControl
public class DPTest : UserControl
{
// dependency property, which type is a class, and this class will be holding other dependency properties
public static readonly DependencyProperty GroupProperty =
DependencyProperty.Register("Group", typeof(DPGroup), typeof(DPTest), new PropertyMetadata(new DPGroup(), OnPropertyChanged));
public DPGroup Group
{
get { return (DPGroup)GetValue(GroupProperty); }
set { SetValue(GroupProperty, value); }
}
// this occurs only when property Group will change, but not when a member of property Group will change
static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DPTest g = d as DPTest;
// etc.
}
}
// a class, where I want to hold my dependency properties
public class DPGroup : DependencyObject
{
public static readonly DependencyProperty MyProperty1Property =
DependencyProperty.RegisterAttached("MyProperty1", typeof(int), typeof(DPGroup), new PropertyMetadata(1, OnPropertyChanged));
public int MyProperty1
{
get { return (int)GetValue(MyProperty1Property); }
set { SetValue(MyProperty1Property, value); }
}
// I would like to notify "the parent" (which means user control "DPTest" ), that member MyProperty1 has changed
static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DPTest g = d as DPTest;
if (g != null) g.textBox1.Text = g.Group.MyProperty1.ToString();
}
}
What I want to achieve is to notify (in design time in XAML) a user control DPTest, that member of Group property (Group.MyProperty1) changed it's value. I managed to make it happen in a run-time, for example by using event handler defined in DPGroup class, but this doesn't work in design-time in xaml.
<Grid x:Name="LayoutRoot" Background="White">
<local:DPTest>
<local:DPTest.Group>
<local:DPGroup MyProperty1="2"/>
</local:DPTest.Group>
</local:DPTest>
</Grid>
It works, but only first time, during creating tag:
<local:DPGroup MyProperty1="2"/>
and after this, changing value of MyProperty1, does not fire DPTest.OnPropertyChange. Probably fires DBGroup.OnPropertyChanged, but this of course does not notify user control DPTest about it. So how to make DPTest know, that the Group.MyProperty1 has changed?
I don't want to make any bindings from MyProperty1 to respective property created inside user control DPTest (not to duplicate properties), the point is to have a group of properties in separate class, so i can use this group more than once, like:
// my UserControl
public class DPTest : UserControl
{
public DPGroup Group1 { ... }
public DPGroup Group2 { ... }
}
I see some analogy to UIElement.RenderTransform (let's say it is my Group property) which holds for example ScaleTransform
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RenderTransform>
<ScaleTransform ScaleX="0.4"/>
</Grid.RenderTransform>
</Grid>
ScaleX is an analogy to MyProperty1. The difference is, that changing value of ScaleX (in XAML) will reflect immediate changes in design-time, and exactly this I am trying to achieve.
I was trying to find a solution in entire google/stack overflow and others, but none found. Everywhere are just examples of creating dependency properties inside a user control.
Thank you for your time.
Any help much appreciated.
edit: based on Harlow Burgess answer, a managed to make a working example in Silverlight. I put the whole solution below as an separate answer.
From: http://msdn.microsoft.com/en-us/library/ms752914.aspx#setting_properties_data_binding
Dependency properties, or the DependencyObject class, do not natively
support INotifyPropertyChanged for purposes of producing notifications
of changes in DependencyObject source property value for data binding
operations. For more information on how to create properties for use
in data binding that can report changes to a data binding target, see
Data Binding Overview.
It would be inefficient to design a system that notifies an entire object graph anytime any property of any subproperty (of any subproperty, of any subproperty, ...) changes. So instead you should use Data Binding to specific properties when you need to do something when that property changes, or if you really want to be notified when any subproperty changes, you should implement INotifyPropertyChanged.
How to: Implement Property Change Notification
Example:
public class DPGroup : DependencyObject, INotifyPropertyChanged
{
public static readonly DependencyProperty MyProperty1Property =
DependencyProperty.RegisterAttached(
"MyProperty1",
typeof(int),
typeof(DPGroup),
new PropertyMetadata(1));
public int MyProperty1
{
get { return (int)GetValue(MyProperty1Property); }
set { SetValue(MyProperty1Property, value); }
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
NotifyPropertyChanged(e.Property.Name);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class DPTest : UserControl
{
public static readonly DependencyProperty GroupProperty =
DependencyProperty.Register(
"Group",
typeof(DPGroup),
typeof(DPTest),
new PropertyMetadata(
new DPGroup(),
new PropertyChangedCallback(OnGroupPropertyChanged)
)
);
public DPGroup Group
{
get { return (DPGroup)GetValue(GroupProperty); }
set { SetValue(GroupProperty, value);}
}
static void OnGroupPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DPTest control = (DPTest)d;
DPGroup oldGroup = e.OldValue as DPGroup;
if (oldGroup != null)
{
oldGroup.PropertyChanged -=new PropertyChangedEventHandler(control.group_PropertyChanged);
}
DPGroup newGroup = e.NewValue as DPGroup;
if (newGroup != null)
{
newGroup.PropertyChanged +=new PropertyChangedEventHandler(control.group_PropertyChanged);
}
control.UpdateTextBox();
}
private void group_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.UpdateTextBox();
}
private void UpdateTextBox()
{
this.textBox1.Text = this.Group.MyProperty1.ToString();
}
private TextBox textBox1;
}
Ok, so based on #Harlow Burgess answer, a managed to make a working example in Silverlight.
Basically the difference is, that in SL, DependencyObject class has no OnPropertyChanged method, so in DPGroup class we cannot override it, but we can attach this method in another way, by:
new PropertyMetadata(1, OnPropertyChanged).
So the DPGroup class will look like this:
public class DPGroup : DependencyObject, INotifyPropertyChanged
{
public static readonly DependencyProperty MyProperty1Property =
DependencyProperty.RegisterAttached(
"MyProperty1",
typeof(int),
typeof(DPGroup),
new PropertyMetadata(1, OnPropertyChanged));
public int MyProperty1
{
get { return (int)GetValue(MyProperty1Property); }
set { SetValue(MyProperty1Property, value); }
}
// static method invoked when MyProperty1 has changed value
static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DPGroup g = d as DPGroup;
if (g != null)
{
g.MyProperty1 = (int)e.NewValue;
// invoking event handler, to notify parent class about changed value of DP
if (g.PropertyChanged != null) g.PropertyChanged(g, null);
}
}
// event handler, for use in parent class
public event PropertyChangedEventHandler PropertyChanged;
}
And the parent class, containing dependency property of type DPGroup:
public partial class DPTest : UserControl
{
public static readonly DependencyProperty GroupProperty =
DependencyProperty.Register(
"Group",
typeof(DPGroup),
typeof(DPTest),
new PropertyMetadata(
new DPGroup(),
new PropertyChangedCallback(OnGroupPropertyChanged)
)
);
public DPGroup Group
{
get { return (DPGroup)GetValue(GroupProperty); }
set { SetValue(GroupProperty, value); }
}
// static method invoked when Group property has changed value
// here we need to attach event handler defined if DPGroup, so it will fire from inside Group property,
// when Group.MyProperty1 will change value
static void OnGroupPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DPTest control = (DPTest)d;
DPGroup oldGroup = e.OldValue as DPGroup;
// removing event handler from prevoius instance of DBGroup
if (oldGroup != null)
oldGroup.PropertyChanged -= new PropertyChangedEventHandler(control.group_PropertyChanged);
DPGroup newGroup = e.NewValue as DPGroup;
// adding event handler to new instance of DBGroup
if (newGroup != null)
newGroup.PropertyChanged += new PropertyChangedEventHandler(control.group_PropertyChanged);
DPTest g = d as DPTest;
if (g != null)
control.UpdateTextBox();
}
private void group_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
UpdateTextBox();
}
// here you can do anything with changed value Group.MyProperty1
private void UpdateTextBox()
{
this.textBox1.Text = this.Group.MyProperty1.ToString();
}
public DPTest()
{
InitializeComponent();
}
}
Now, the XAML part for DPTest:
<UserControl x:Class="Silverlight_Workbench_2.DPTest"
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:local="clr-namespace:Silverlight_Workbench_2"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" >
<Grid x:Name="LayoutRoot" Background="White">
<TextBox Height="23" HorizontalAlignment="Left" Margin="76,61,0,0"
x:Name="textBox1" VerticalAlignment="Top" Width="120" />
</Grid>
</UserControl>
Finally, we can embed our DPTest in some content of any control, for example in a Grid of another user control:
<UserControl x:Class="Silverlight_Workbench_2.DPTestMain"
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:local="clr-namespace:Silverlight_Workbench_2"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">
<Grid x:Name="LayoutRoot" Background="White">
<local:DPTest>
<local:DPTest.Group>
<!--here we can change value, and it will be reflected in design window
as a text in textBox1-->
<local:DPGroup MyProperty1="8"/>
</local:DPTest.Group>
</local:DPTest>
</Grid>
</UserControl>
That is all, thanks again to the Harlow Burgess for the help!
Related
I have a class called TxtBox with an attached Property:
public class TxtBox
{
public static readonly DependencyProperty TypeProperty = DependencyProperty.RegisterAttached(
"Type", typeof (Enums.FieldType), typeof (TextBox), new PropertyMetadata(default(Enums.FieldType),OnTypeChanged));
public static void SetType(DependencyObject element, Enums.FieldType value)
{
element.SetValue(TypeProperty, value);
}
public static Enums.FieldType GetType(DependencyObject element)
{
return (Enums.FieldType) element.GetValue(TypeProperty);
}
private static void OnTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var src = (TextBox) d; //(FrameworkElement)d;
var binding = BindingOperations.GetBinding(src, TextBox.TextProperty);
if (binding != null) //Binding here is always null ?????????
{
binding.Converter = new NumberConverter();
binding.ConverterParameter = e.NewValue;
}
}
}
At MainWindow.xaml :
<Grid Margin="10">
<TextBox Text="{Binding RequestNo}" att:TxtBox.Type="Number" />
<\Grid>
I need to assign the Converter and ConverterParameter for the TextProperty once I have set the type for the textbox control through the attached property (Type). When the OnTypeChanged method fires, I can't get the Binding, as it is always null !!!
Thanks in advance :)
Your attached property is being set before the Binding is applied to the Text property of the Text box. You can work around this by attempting to update the Binding when the value of Text changes:
private static void OnTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var src = (TextBox)d;
var dpd = DependencyPropertyDescriptor.FromProperty(TextBox.TextProperty, typeof(TextBox));
dpd.AddValueChanged(src, UpdateBindingHandler);
UpdateBinding(src);
}
protected static void UpdateBindingHandler(object sender, EventArgs e)
{
UpdateBinding((TextBox)sender);
}
private static void UpdateBinding(TextBox tbox)
{
var binding = BindingOperations.GetBinding(tbox, TextBox.TextProperty);
if (binding != null)
{
binding.Converter = new NumberConverter();
binding.ConverterParameter = GetType(tbox);
var dpd = DependencyPropertyDescriptor.FromProperty(TextBox.TextProperty, typeof(TextBox));
// Don't do this every time the value changes, only the first time
// it changes after TxtBox.Type has changed.
dpd.RemoveValueChanged(tbox, UpdateBindingHandler);
}
}
When you do that, you'll find that your whole design is flawed: You can't alter a Binding once it's been used. It throws an exception.
You might be able to get away with creating a new binding, clone the properties of the old one, and put a converter on it. Bindings have a lot of properties, though, and if there's already a converter, you'll need to replace it with a chain converter that preserves that one while adding yours.
I'm not sure this feature is going to work out.
Finally,I have got the solution, I changed the design as Mr Peter Duniho advised me to write a markup extension to take the place of the {Binding}
public class TextBoxTypeExtension:MarkupExtension
{
private readonly Binding _binding;
public TextBoxTypeExtension(Binding binding,Enums.FieldType type)
{
_binding = binding;
_binding.Converter = new NumberConverter();
_binding.ConverterParameter = type;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return _binding.ProvideValue(serviceProvider);
}
}
At MainWindow.xaml :
<TextBox MaxLength="10" Grid.Row="1" Grid.Column="1"
Text="{extension:TextBoxType {Binding Request.RequestNo},Number}"/>
Reference:
MarkupExtension that uses a DataBinding value
I'm trying to implement the example at:
https://github.com/olohmann/WpfRxControls
There are three parts to the custom control:
PART_TextBox
PART_Popup
PART_ListBox
Relevant source:
https://github.com/olohmann/WpfRxControls/blob/master/WpfRxControls/AutoCompleteTextBox.cs
https://github.com/olohmann/WpfRxControls/blob/master/WpfRxControls/Themes/Generic.xaml
All the pieces are in place, and the code using the new control is as follows:
<ctrls:AutoCompleteTextBox
Grid.Row="1"
AutoCompleteQueryResultProvider="{Binding AutoCompleteQueryResultProvider}"
Margin="10" FontSize="20" PopupHeight="300">
</ctrls:AutoCompleteTextBox>
I just need to hook into the ListBox's SelectionChanged event in my pages XAML / ViewModel, how can this be accomplished?
Edit: In XAML / VM, not view code behind. Thus far all view code behinds are empty and I'd like to keep it that way.
I thought there was some way to override PART_ListBox in a ControlTemplate override in MainWindow.XAML?
Edit: Final solution, thanks to mm8
In the AutoCompleteTextBox.cs, create a dependency property of type ICommand:
public const string AutoCompleteSelectionChangedPropertyName = "AutoCompleteSelectionChangedCommand";
public ICommand AutoCompleteSelectionChangedCommand
{
get { return (ICommand) GetValue(AutoCompleteSelectionChangedProperty); }
set { SetValue(AutoCompleteSelectionChangedProperty, value);}
}
public static readonly DependencyProperty AutoCompleteSelectionChangedProperty = DependencyProperty.Register(
AutoCompleteSelectionChangedPropertyName,
typeof(ICommand),
typeof(AutoCompleteTextBox));
In the SetResultText method:
AutoCompleteSelectionChangedCommand?.Execute(autoCompleteQueryResult);
View / ViewModel usage:
<ac:AutoCompleteTextBox Name="AutoComplete"
AutoCompleteQueryResultProvider="{Binding AutoCompleteQueryResultProvider}"
FontSize="12"
AutoCompleteSelectionChangedCommand="{Binding CommandEditValueChanged}">
</ac:AutoCompleteTextBox>
public ICommand CommandEditValueChanged { get; set; }
public MainWindowViewModel(){
CommandEditValueChanged = new DelegateCommand<object>(OnEditValueChanged);
}
private void OnEditValueChanged(object result){
// do stuff
}
You could handle the Loaded event of the AutoCompleteTextBox in the view, get a reference to the PART_ListBox in the control template using the FindName method and then hook up an event handler for the SelectionChanged event of the ListBox:
<ctrls:AutoCompleteTextBox
Grid.Row="1"
AutoCompleteQueryResultProvider="{Binding AutoCompleteQueryResultProvider}"
Margin="10" FontSize="20" PopupHeight="300" Loaded="AutoCompleteTextBox_Loaded">
</ctrls:AutoCompleteTextBox>
private void AutoCompleteTextBox_Loaded(object sender, RoutedEventArgs e)
{
AutoCompleteTextBox actb = sender as AutoCompleteTextBox;
ListBox lb = actb.Template.FindName("PART_ListBox", actb) as ListBox;
if (lb != null)
{
lb.SelectionChanged += (ss, ee) =>
{
MainWindowViewModel vm = DataContext as MainWindowViewModel;
//invoke a command of the view model or do whatever you want here...
var selectedItem = lb.SelectedItem;
};
}
}
Your view model class has no (and shouldn't have any) reference nor knowledge about the ListBox that is part of the template of the control.
I thought there was some way to override PART_ListBox in a ControlTemplate override in MainWindow.XAML?
Then you will have to override/re-define the entire ControlTemplate of the AutoCompleteTextBox control which seems a bit unnecessary.
MVVM is not about eliminating code from the views - it's about separation of concerns and whether you hook up an event handler from the XAML markup of the view or the code-behind of the very same view makes no difference at all as far as the design pattern is concerned.
Edit: But if you want to keep the code-behind classes clean you could implement this using an attached behaviour:
public class AutoCompleteBoxBehavior
{
public static ICommand GetSelectionChangedCommand(AutoCompleteTextBox actb)
{
return (ICommand)actb.GetValue(SelectionChangedCommandProperty);
}
public static void SetSelectionChangedCommand(AutoCompleteTextBox actb, ICommand value)
{
actb.SetValue(SelectionChangedCommandProperty, value);
}
public static readonly DependencyProperty SelectionChangedCommandProperty =
DependencyProperty.RegisterAttached(
"SelectionChangedCommand",
typeof(ICommand),
typeof(AutoCompleteBoxBehavior),
new UIPropertyMetadata(null, OnHandleSelectionChangedEvent));
private static void OnHandleSelectionChangedEvent(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ICommand command = e.NewValue as ICommand;
if(command != null)
{
AutoCompleteTextBox actb = d as AutoCompleteTextBox;
actb.Loaded += (ss, ee) =>
{
ListBox lb = actb.Template.FindName("PART_ListBox", actb) as ListBox;
if (lb != null)
{
lb.SelectionChanged += (sss, eee) =>
{
command.Execute(null);
};
}
};
}
}
}
<ctrls:AutoCompleteTextBox
Grid.Row="1"
AutoCompleteQueryResultProvider="{Binding AutoCompleteQueryResultProvider}"
Margin="10" FontSize="20" PopupHeight="300"
local:AutoCompleteBoxBehavior.SelectionChangedCommand="{Binding YourCommand}">
</ctrls:AutoCompleteTextBox>
Introduction to Attached Behaviors in WPF: https://www.codeproject.com/Articles/28959/Introduction-to-Attached-Behaviors-in-WPF
I am using a DevExpress ComboboxEdit object to get multiple selection from the user. My problem is that I am not sure what type of object will come back once a selection has been done.
I have read this one, and came up with the code below, but I am not sure what I am missing. (I also don't know exactly what a DependencyProperty is, but would like to avoid too many objects)
<Window x:Class = "Demo.MainWindow"
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:local = "clr-namespace:Demo"
xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
xmlns:dxl="http://schemas.devexpress.com/winfx/2008/xaml/layoutcontrol"
xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<StackPanel>
<dxe:ComboBoxEdit ItemsSource="{Binding Path=MyList}"
IsTextEditable="False"
EditValue="{Binding Path=MySelectedList, Mode=TwoWay}"
Name="abc">
<dxe:ComboBoxEdit.StyleSettings>
<dxe:CheckedComboBoxStyleSettings/>
</dxe:ComboBoxEdit.StyleSettings>
</dxe:ComboBoxEdit>
<Button Click="showSelected" Content="Show selected items" />
</StackPanel>
</Window>
MainWindow.xaml.cs
using System.Collections.Generic;
using System.Windows;
using System.Text;
namespace Demo
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
private System.Collections.Generic.IList<string> _myList;
private System.Collections.Generic.IList<string> _mySelectedList; // This has probably the wrong type.
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public IList<string> MyList
{
get
{
return _myList;
}
set
{
_myList = value;
this.RaisePropertyChanged("MyList");
}
}
public IList<string> MySelectedList
{
get
{
return _mySelectedList;
}
set
{
_mySelectedList = value;
this.RaisePropertyChanged("MySelectedList");
}
}
private void showSelected(object sender, RoutedEventArgs e)
{
StringBuilder sb = new StringBuilder();
foreach(string s in this.MySelectedList)
{
sb.Append(s);
}
System.Windows.MessageBox.Show(sb.ToString());
// This MessageBox show show whatever is checked.
}
public MainWindow()
{
MySelectedList = new System.Collections.Generic.List<string>();
MyList = new System.Collections.Generic.List<string>();
MyList.Add("a");
MyList.Add("b");
MyList.Add("c");
MyList.Add("d");
DataContext = this;
}
}
}
When I run it and click the combobox, then a red X appears and says that The type System.Collection.Generic.List´1[System.Object] could not be converted. And the MessageBox is always empty.
You do not have INotifyPropertyChanged implemented on your MainWindow, but that may not be the only issue. I would read up on Dependency Properties and Data Binding before you really try to tinker with WPF. If you do not understand those concepts everything will be difficult and confusing.
EDIT
They are using a DependencyProperty (As you mentioned) it seems. But anyway, this is how you would implement one
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register("SelectedItems", typeof(IList), typeof(MainWindow), new PropertyMetadata(null, new PropertyChangedCallback(OnSelectedItemsChanged)));
private static void OnSelectedItemsChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
MainWindow mainWindow = o as MainWindow;
if (mainWindow != null)
mainWindow.OnSelectedItemsChanged((IList)e.OldValue, (IList)e.NewValue);
}
protected virtual void OnSelectedItemsChanged(IList oldValue, IList newValue)
{
// Add your property changed side-effects. Descendants can override as well.
}
public IList SelectedItems
{
// IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
get
{
return (IList)GetValue(SelectedItemsProperty);
}
set
{
SetValue(SelectedItemsProperty, value);
}
}
Notice it needs to be of type IList and you will need to cast to type string
Also, remove the Mode=TwoWay as it is not needed in your binding.
<dxe:ComboBoxEdit ItemsSource="{Binding MyList}" EditValue="{Binding SelectedItems}" >
<dxe:ComboBoxEdit.StyleSettings>
<dxe:CheckedComboBoxStyleSettings/>
</dxe:ComboBoxEdit.StyleSettings>
</dxe:ComboBoxEdit>
You also do not need INotifyPropertyChanged that was my mistake. I thought you were doing traditional binding.
EditValue property contains a list of objects, so your code in VM should look like this:
private List<object> _mySelectedList;
public List<object> MySelectedList
{
get
{
return _mySelectedList;
}
set
{
_mySelectedList = value;
this.RaisePropertyChanged("MySelectedList");
}
}
Or you can write EditValue converter, example you will find here.
I want to display a list of objects in a LongListSelector, and format them with a DataTemplate. To properly use MVVM I'd like to have a ViewModel in this DataTemplate.
Creating of this ViewModel is no problem, but how do I pass the Item to the ViewModel?
I'm using this code:
<Controls:LongListSelector
ItemsSource="{Binding MyItems}" Margin="0" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch" >
<Controls:LongListSelector.DataContext>
<viewmodel:MyListOfItemsViewModel />
</Controls:LongListSelector.DataContext>
<Controls:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="CurTemplate">
<Grid Margin="10" >
<Grid.DataContext>
<viewmodel:MyViewModel MyItem="{Binding Path=DataContext,ElementName=CurTemplate}" />
</Grid.DataContext>
But alas, the only thing that is set for MyItem is null, and this is never updated to the real value. I found out that later in the process (after the initial setting of MyItem CurTemplate does have a valid DataContext, but this is not sent to my ViewModel. Am I missing something here?
For completeness the code for MyViewModel:
public static DependencyProperty MyItemProperty = DependencyProperty.Register("MyItem", typeof(object), typeof(MyViewModel), new PropertyMetadata("asd", ItemChanged));
private static void ItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
System.Diagnostics.Debugger.Break(); // to set when something is set
// called once, NewValue is null
}
public object MyItem
{
get
{
return (object)GetValue(MyItemProperty);
}
set
{
SetValue(MyItemProperty, value);
RaisePropChangeEvent("MyItem");
}
}
I did a lot of searching and fiddling around, but I'm pretty sure this is just a minor thing that is missing here. I would be very glad if you could help me out here...
EDIT: Solved
I solved my problem by using {Binding Path=Content,RelativeSource={RelativeSource Mode=TemplatedParent}} as binding for the viewmodel. I have no idea why this works with Content, but not with DataContext...
Thanks for your help, robertftw, your linked post brought me to the right track!
Seems like the problem I had a few days ago:
Binding does not update usercontrol property correctly MVVM
public static DependencyProperty MyItemProperty = DependencyProperty.Register("MyItem", typeof(object), typeof(MyViewModel), new PropertyMetadata(string.Empty, ItemChanged));
private static void ItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var obj = d as MyViewModel;
obj.RaisePropChangeEvent("MyItem");
}
The problem I had is that the set of MyItem isn't actually called.
What are you trying to do?
Do you just want to interact to a selection change and have the selected item in the LongListSelector being pushed to the ViewModel?
If so... I'm using an extension for this scenario. The only thing with such an extension is that setting the current item from the ViewModel is not ported back to the view ( but didn't need that ).
The ViewModel change is
public RelayCommand<MyItemType> ViewModelCommand
The XAML change is
<phone:LongListSelector extensions:LongListSelectorExtension.Command="{Binding ViewModelCommand}" />
The extension
public static class LongListSelectorExtension
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command",
typeof(ICommand), typeof(LongListSelectorExtension),
new PropertyMetadata(null, OnCommandChanged));
public static ICommand GetCommand(LongListSelector selector)
{
return (ICommand)selector.GetValue(CommandProperty);
}
public static void SetCommand(LongListSelector selector, ICommand value)
{
selector.SetValue(CommandProperty, value);
}
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var selector = d as LongListSelector;
if (selector == null)
{
throw new ArgumentException(
"You must set the Command attached property on an element that derives from LongListSelector.");
}
var oldCommand = e.OldValue as ICommand;
if (oldCommand != null)
{
selector.SelectionChanged -= OnSelectionChanged;
}
var newCommand = e.NewValue as ICommand;
if (newCommand != null)
{
selector.SelectionChanged += OnSelectionChanged;
}
}
private static void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selector = sender as LongListSelector;
var command = GetCommand(selector);
if (command != null && selector.SelectedItem != null)
{
command.Execute(selector.SelectedItem);
}
selector.SelectedItem = null;
}
}
Put simply, I can create 2 dependency properties in a WPF control and put code in each property change notification to change the other property (i.e PropA change sets PropB and PropB change sets PropA).
I would expect this to disappear up its own backside but WPF seems to handle it nicely. That's actually very handy for my purposes but I can't find this behaviour documented anywhere.
So what's going on? Does the WPF dependency property change notification system guard against reentrancy?
Representative code follows:
XAML:
<Window x:Class="WPFReentrancy1.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">
<Grid>
<TextBox Text="{Binding PropB, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Window>
Code behind:
public partial class MainWindow : Window
{
public string PropA
{
get { return (string)GetValue(PropAProperty); }
set { SetValue(PropAProperty, value); }
}
public static readonly DependencyProperty PropAProperty =
DependencyProperty.Register("PropA", typeof (string), typeof (MainWindow),new UIPropertyMetadata("0", PropAChanged));
public string PropB
{
get { return (string)GetValue(PropBProperty); }
set { SetValue(PropBProperty, value); }
}
public static readonly DependencyProperty PropBProperty =
DependencyProperty.Register("PropB", typeof (string), typeof (MainWindow), new UIPropertyMetadata("", PropBChanged));
private static void PropBChanged(DependencyObject lDependencyObject, DependencyPropertyChangedEventArgs lDependencyPropertyChangedEventArgs)
{
((MainWindow) lDependencyObject).PropA = (string) lDependencyPropertyChangedEventArgs.NewValue;
}
private static void PropAChanged(DependencyObject lDependencyObject, DependencyPropertyChangedEventArgs lDependencyPropertyChangedEventArgs)
{
((MainWindow) lDependencyObject).PropB =
double.Parse((string) lDependencyPropertyChangedEventArgs.NewValue).ToString("0.000");
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
PropA = "1.123";
}
}
Those callbacks are only fired if the property changed, your code does not create an infinite loop of different values.
Try this and you will get a SO exception:
private static readonly Random _random = new Random();
private static void PropBChanged(DependencyObject lDependencyObject, DependencyPropertyChangedEventArgs lDependencyPropertyChangedEventArgs)
{
((MainWindow)lDependencyObject).PropA = _random.Next().ToString();
}
private static void PropAChanged(DependencyObject lDependencyObject, DependencyPropertyChangedEventArgs lDependencyPropertyChangedEventArgs)
{
((MainWindow)lDependencyObject).PropB = _random.Next().ToString();
}