Setting XAML property value into user control from a consumer view - wpf

I have a custom UserControl that contains a grid ...I wish to set the ItemsSource property of that grid by xaml code of of a data template in a resource dictionary...
then I have used dependency property... this is my implementation...
public partial class MyControlGrid : UserControl
{
// Dependency Property
public static readonly DependencyProperty MyItemSourceProperty =
DependencyProperty.Register("MyItemSource", typeof(ICollectionView),
typeof(MyControlGrid), new FrameworkPropertyMetadata(null, OnMyItemSourcePropertyChanged));
IDictionary<string, string> _columns = new Dictionary<string, string>();
private static void OnMyItemSourcePropertyChanged(DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{
// When the color changes, set the icon color PlayButton
MyControlGrid muc = (MyControlGrid)obj;
ICollectionView value = (ICollectionView)args.NewValue;
if (value != null)
{
muc.MyGridControl.ItemsSource = value;
}
}
public ICollectionView MyItemSource
{
get
{
return (ICollectionView)GetValue(MyItemSourceProperty);
}
set
{
SetValue(MyItemSourceProperty, value);
//OnTargetPowerChanged(this, new DependencyPropertyChangedEventArgs(TargetPowerProperty, value, value));
// Old value irrelevant.
}
}
public MyControlGrid()
{
InitializeComponent();
}
}
this is the user control xaml code
<UserControl x:Class="GUI.Design.Templates.MyControlGrid"
Name="MyListControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfTkit="http://schemas.microsoft.com/wpf/2008/toolkit"
xmlns:Templates="clr-namespace:Emule.GUI.Design.Templates">
<StackPanel>
<WpfTkit:DataGrid ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Templates:MyControlGrid}}, Path=MyItemSource}"
x:Name="MyGridControl"
<StackPanel>
this is the binding path expression I use
<basic:MyControlGrid MyItemSource="{Binding MyDataContextVisibleCollection}"/>
this dont work and wpf output window dont show me any errors
note that, naturally, if I bind this directly in the user controls work fine
<WpfTkit:DataGrid ItemsSource="{Binding MyDataContextVisibleCollection}"
Waths I wrong?
thanks
p.s. sorry for my english

this
answer show me the way
use of PropertyChangedCallback work fine with my code:
public static readonly DependencyProperty MyItemSourceProperty =
DependencyProperty.Register("MyItemSource", typeof(IEnumerable),
typeof(MyControlGrid), new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(MyControlGrid.OnItemsSourceChanged)));
alternatively I have to remove comment on OnTargetPowerChanged and fire the property changed event
set
{
SetValue(MyItemSourceProperty, value);
//OnTargetPowerChanged(this, new DependencyPropertyChangedEventArgs(TargetPowerProperty, value, value));
// Old value irrelevant.
}
correct with
public ICollectionView MyItemSource
{
get
{
return (ICollectionView)GetValue(MyItemSourceProperty);
}
set
{
SetValue(MyItemSourceProperty, value);
OnItemsSourceChanged(this, new DependencyPropertyChangedEventArgs(MyItemSourceProperty, value, value));
}
}

Related

DependencyProperty and caliburn, view model not updated by dependency property

I have a view which wraps a TreeView, called MbiTreeView. I want to get the selected item from the (wrapped) tree view in the view model.
The 'parent' user control which uses this custom user control:
<UserControl [...]>
<views:MbiTreeView
Grid.Row="0"
cal:Bind.Model="{Binding TreeViewModel}"
SelectedItem="{Binding SelectedItem}">
</views:MbiTreeView>
</UserControl>
The parent user control is bound to this view model:
internal sealed class SomeViewModel : PropertyChangedBase
{
public object SelectedItem
{
get => _selectedItem;
set
{
_selectedItem = value;
NotifyOfPropertyChange(() => SelectedItem);
}
}
public IMbiTreeViewModel TreeViewModel { get; }
public SomeViewModel(
IMbiTreeViewModel treeViewModel)
{
TreeViewModel = treeViewModel;
}
}
The MbiTreeView user control is rather straight forward. It subscribes to the selection changed event, and defines a few templates (not relevant for this question, so left them out in the question)
<TreeView ItemsSource="{Binding Items}" SelectedItemChanged="TreeView_OnSelectedItemChanged">
iew.ItemContainerStyle>
The code behind declares the dependency property:
public partial class MbiTreeView
{
public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register(
nameof(SelectedItem),
typeof(object),
typeof(MbiTreeView),
null);
public object SelectedItem
{
get => GetValue(SelectedItemProperty);
set => SetValue(SelectedItemProperty, value);
}
public MbiTreeView()
{
InitializeComponent();
}
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SelectedItem = e.NewValue;
}
}
when I start the application, I can navigate through the tree view items. When I click on a treeview node, then the OnSelectedItemChanged event fires (I get into my breakpoint there). So everything goes fine up and until setting the value in the dependency property SelectedItem.
Then I would expect that the xaml binding gets notified, and updates the view model. But that never happens.
I am getting nowhere with this, help is greatly appreciated.
The SelectedItem Binding should be TwoWay:
<views:MbiTreeView ...
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"/>
You could declare the property like shown below to make to bind TwoWay by default.
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register(
nameof(SelectedItem),
typeof(object),
typeof(MbiTreeView),
new FrameworkPropertyMetadata(
null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

Binding to dependency property in usercontrol

I have a UserControl that contains a ListBox and I want to track the SelectedItems of that listbox.
The UserControl has a DP "SelectedItemsList" that is defined like this
public static DependencyProperty SelectedItemsListProperty = DependencyProperty.Register(
"SelectedItemsList",
typeof (IList),
typeof (MyListControl),
new FrameworkPropertyMetadata(null,
OnSelectedItemsChanged));
In the listbox' Item "SelectionChanged" event, I want to save the selected items to the DP. This is triggered whenever I change the selection in the listbox.
private void OnItemSelectionChanged(object sender, SelectionChangedEventArgs e)
{
SelectedItemsList = this.myListBox.SelectedItems;
}
In my view that contains the "MyListControl" I create a binding to my viewmodel that want to use the selected items.
<controls:MyListControl
Source="{Binding SomeItemsList, UpdateSourceTrigger=PropertyChanged}"
SelectedItemsList="{Binding SelectedItems, UpdateSourceTrigger=PropertyChanged}"/>
My problem is, that the DP SelectedItemsList never gets updated. The PropertyChangeCallback "OnSelectedItemsChanged" of the DP is only triggered when I initially load the lists content. The value of the SelectedItemsList is always null.
I am aware that this question is similar to Dependency property callback does not work, but the answers posted there do not solve my problem.
What am I missing here?
Thanks,
Edit (2015-09-10):
Thank you all for your comments. I found a solution that fits my needs:
First of all I created a custom listbox control that provided the list of selected items in a dependency property (very similar to Select multiple items from a DataGrid in an MVVM WPF project).
public class CustomListBox : ListBox
{
public static readonly DependencyProperty SelectedItemsListProperty =
DependencyProperty.Register("SelectedItemsList",
typeof (IList),
typeof (CustomListBox),
new PropertyMetadata(null));
public CustomListBox()
{
SelectionChanged += OnSelectionChanged;
}
public IList SelectedItemsList
{
get { return (IList)GetValue(SelectedItemsListProperty); }
set { SetValue(SelectedItemsListProperty, value); }
}
void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SelectedItemsList= new ArrayList(this.SelectedItems);
}
}
I am not happy yet with the "new ArrayList"-part, but if in my viewmodel's property setter I want to check for equality, SelectedItemsList can not be a reference of SelectedItems. The previous and the new value would always be the same.
Then I reduced the item selection parts of my UserControl "MyListControl" simply to the dependency property itself:
public static DependencyProperty SelectedItemsProperty = DependencyProperty.Register(
"SelectedItems",
typeof (IList),
typeof (MyListControl),
new FrameworkPropertyMetadata(null));
public IList SelectedItems
{
get
{
return (IList)GetValue(SelectedItemsProperty);
}
set
{
SetValue(SelectedItemsProperty, value);
}
}
and modified the xaml of the MyListControl:
<controls:CustomListBox
SelectionMode="Extended"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:MyListControl}},
Path=Source, UpdateSourceTrigger=PropertyChanged}"
SelectedItemsList="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:MyListControl}},
Path=SelectedItems, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
>
The property in my ViewModel looks like
public IList SelectedObjects
{
get { return _selectedObjects; }
set { if (this._selectedObjects != value)
{
this._selectedObjects = value;
OnPropertyChanged(SelectedObjectsProperty);
}
}
}
It was important that the type of this property is IList, otherwise the value in the setter would always be null.
And in the view's xaml
<controls:MyListControl
Source="{Binding CurrentImageList, UpdateSourceTrigger=PropertyChanged}"
SelectedItems="{Binding SelectedObjects, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
/>
I just had the same problem today, unfortunately, when you are assigning to SelectedItemsList a value, WPF seems to unbind it. To fix it, I update the value in the binded item. I know that it is not the best solution in the world but for me it works.
In this case the code would looked like this:
private void OnItemSelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SetPropertyValue(
this.GetBindingExpression(SelectedItemsListProperty),
this.myListBox.SelectedItems);
}
private void SetPropertyValue(BindingExpression bindingExpression, object value)
{
string path = bindingExpression.ParentBinding.Path.Path;
var properties = new Queue<string>(
path.Split(
new[]
{
'.'
}).ToList());
this.SetPropertyValue(bindingExpression.DataItem, bindingExpression.DataItem.GetType(), properties, value);
}
private void SetPropertyValue(object destination, Type type, Queue<string> properties, object value)
{
PropertyInfo property = type.GetProperty(properties.Dequeue());
if (property != null && destination != null)
{
if (properties.Count > 0)
{
this.SetPropertyValue(property.GetValue(destination), property.PropertyType, properties, value);
}
else
{
property.SetValue(destination, value);
}
}
}
You need to bind your Listbox' SelectedItems to the DP SelectedItemsList to propagate the user selection to the DP. The binding you already have will then pass the changes on to the viewmodel, but I think you will need a binding mode 'twoway' instead of UpdateSourceTrigger.
And don't use the PropertyChangeCallback in your DP: Changing the SelectedItemsList if the SelectedItemsListProperty has changed makes no sense. (Usually the former is a wrapper property of the latter.)

Reference Usercontrol in a DataTemplate from the DataTemplate's datatype class

I have a datatemplate like this:
<DataTemplate DataType="{x:Type mvvm:ComponentViewModel}">
<v:UCComponents></v:UCComponents>
</DataTemplate>
UCComponent is a usercontrol with a public property called ID. ComponentViewModel also has a property called ID. I would like to set UCComponent ID property in the setter of the ViewModel's property. How can I do that?
This is what I've tried:
private string _ID;
public string ID
{
set { ((UCComponents)((DataTemplate)this).LoadContent()).ID = value; }
}
Error: this cannot be converted from ComponentViewModel to DataTemplate. Any help will be appreciated.
I'm not keeping true to MVVM design pattern, which is probably the reason for my frustration, but is there a way to access the UserControl used in the template?
Amanda
Thanks for the help, but it doesn't work. The getter and setter of ID is never called. This is my code:
public static DependencyProperty IDProperty = DependencyProperty.Register("ID", typeof(Guid), typeof(UCComponents));
public Guid ID
{
get
{ return (Guid)GetValue(IDProperty); }
set
{
SetValue(IDProperty, value);
LoadData();
}
}
I can't make the UserControl a DependencyObject. Is that perhaps the problem?
You can add a dependency property on your user control (UCComponents.xaml.cs) as follows
public static DependencyProperty IDProperty = DependencyProperty.Register(
"ID",
typeof(Object),
typeof(BindingTestCtrl));
public string ID
{
get
{
return (string)GetValue(IDProperty);
}
set
{
SetValue(IDProperty, value);
}
}
then you can bind it using
<DataTemplate DataType="{x:Type mvvm:ComponentViewModel}">
<v:UCComponents ID="{Binding ID}" />
</DataTemplate>
Another solution would be to handle the DataContextChanged event on your user control with something like
private ComponentViewModel _data;
private void UserControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
_data = e.NewValue as ComponentViewModel;
this.ID = _data.ID;
}
Thanks Jim,
I got it to work this way: In my UserControl:
public static DependencyProperty IDProperty = DependencyProperty.Register("ID", typeof(Guid), typeof(UCComponents));
public Guid ID
{
get
{ return (Guid)GetValue(IDProperty); }
set
{
SetValue(IDProperty, value);
LoadData();
}
}
private void UserControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
_data = e.NewValue as ComponentViewModel;
this.ID = _data.ID;
}
In my ViewModel (used as the template type):
public ComponentViewModel(Guid id)
{
DisplayName = "Component";
Glyph = new BitmapImage(new Uri("/Remlife;component/Images/Toolbox/Control.png", UriKind.Relative));
ID = id;
}
And in my Resources file:
<DataTemplate DataType="{x:Type mvvm:ComponentViewModel}">
<v:UCComponents ID="{Binding ID}"/>
</DataTemplate>
Somehow I need to still pass the ID to the ViewModel in it's constructor. Why? I'm not sure, but at least it works now. Thanks for your help.

Binding WP7 Maps control to ViewModel, Problem with MapMode

I am trying to reproduce the BingMaps sample of the Windows Phone 7 trainingkit:
http://msdn.microsoft.com/en-us/wp7trainingcourse_usingbingmapslab_topic2.aspx#_Toc271039352
but instead of wiring everything in codebehind i'd like to use a viewmodel.
Everything works fine except binding to the Mode property (aerial or road) causes a XamlParseException.
Is there a problem because it isn't a simple property?
This is the original Xaml:
<my:Map Name="Map"
CredentialsProvider="{Binding CredentialsProvider}">
<my:Map.Mode>
<my:AerialMode ShouldDisplayLabels="True" />
</my:Map.Mode>
</my:Map>
The Map.Mode can be changed from codebehind.
Instead I am trying the following:
<my:Map x:Name="Map"
CredentialsProvider="{Binding CredentialsProvider}"
ZoomLevel="{Binding Zoom, Mode=TwoWay}"
Center="{Binding Center, Mode=TwoWay}"
Mode="{Binding MapMode}" />
and the important part of the viewmodel:
private MapMode _mapMode = new AerialMode(true);
public MapMode MapMode
{
get { return _mapMode; }
set
{
_mapMode = value;
RaisePropertyChanged("MapMode");
}
}
private void ChangeMapMode()
{
if (MapMode is AerialMode)
{
MapMode = new RoadMode();
}
else
{
MapMode = new AerialMode(true);
}
}
Thanks for your help!
Solved.
"Mode" isn't a dependency property. So it cannot be bound.
My workaround:
added dependency property to view (=Page)
bound dependency property to property in viewmodel (via code in the constructor)
Set Mode of Map control in the propertyChanged callback handler
//Constructor
public MainPage()
{
InitializeComponent();
DataContext = new MainViewModel();
Binding b = new Binding("MapMode");
this.SetBinding(MapModeProperty, b);
}
//DependencyProperty. No need for corresponding CLR-property.
public static readonly DependencyProperty MapModeProperty =
DependencyProperty.Register("MapMode", typeof(MapMode), typeof(MainPage),
new PropertyMetadata(OnMapModeChanged));
//Callback
private static void OnMapModeChanged(DependencyObject element,
DependencyPropertyChangedEventArgs e)
{
((MainPage)element).Map.Mode = e.NewValue as MapMode;
}
Hope this one will help others!
I suspect you'll need to use a converter with your binding.

ag_e_parser_bad_property_value Silverlight Binding Page Title

XAML:
<navigation:Page ... Title="{Binding Name}">
C#
public TablePage()
{
this.DataContext = new Table()
{
Name = "Finding Table"
};
InitializeComponent();
}
Getting a ag_e_parser_bad_property_value error in InitializeComponent at the point where the title binding is happening. I've tried adding static text which works fine. If I use binding anywhere else eg:
<TextBlock Text="{Binding Name}"/>
This doesn't work either.
I'm guessing it's complaining because the DataContext object isn't set but if I put in a break point before the InitializeComponent I can confirm it is populated and the Name property is set.
Any ideas?
You can only use data binding on properties that are supported by DependencyProperty. If you take a look at the docs for TextBlock for example you will find that the Text property has a matching TextProperty public static field of type DependencyProperty.
If you look at the docs for Page you will find that there is no TitleProperty defined, the Title property is therefore not a dependency property.
Edit
There is no way to "override" this however you could create an attached property:-
public static class Helper
{
#region public attached string Title
public static string GetTitle(Page element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
return element.GetValue(TitleProperty) as string;
}
public static void SetTitle(Page element, string value)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
element.SetValue(TitleProperty, value);
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.RegisterAttached(
"Title",
typeof(string),
typeof(Helper),
new PropertyMetadata(null, OnTitlePropertyChanged));
private static void OnTitlePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Page source = d as Page;
source.Title = e.NewValue as string;
}
#endregion public attached string Title
}
Now your page xaml might look a bit like:-
<navigation:Page ...
xmlns:local="clr-namespace:SilverlightApplication1"
local:Helper.Title="{Binding Name}">
Add the following to MyPage.xaml.cs:
public new string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title",
typeof(string),
typeof(Page),
new PropertyMetadata(""));
Once you add this property (dependency property) to your code behind, your code will work as normal.

Resources