Binding a Resource to a View Model - wpf

How do you bind data from the view model into an object in the resources of the user control? Here is a very abstract example:
<UserControl ...
xmlns:local="clr-namespace:My.Local.Namespace"
Name="userControl">
<UserControl.Resources>
<local:GroupingProvider x:Key="groupingProvider" GroupValue="{Binding ???}" />
</UserControl.Resources>
<Grid>
<local:GroupingConsumer Name="groupingConsumer1" Provider={StaticResource groupingProvider"} />
<local:GroupingConsumer Name="groupingConsumer2" Provider={StaticResource groupingProvider"} />
</Grid>
</UserControl>
How do I bind GroupValue to a property in the view model behind this view. I've tried the following:
<local:GroupingProvider x:Key="groupingProvider" GroupValue="{Binding ElementName=userControl, Path=DataContext.Property}"/>
But this doesn't work.
Edit:
GroupProvider extends DependencyObject and GroupValue is the name of a DependencyProperty. I'm getting the following error:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=DataContext.Property; DataItem=null; target element is 'GroupingProvider' (HashCode=47478197); target property is 'GroupValue' (type 'TimeSpan')
This seems to suggest that it cannot find userControl.
More Edit:
Nobody has an answer to my question? Is there not a way to do this?

I know its a bit late, but i had the same problem. Ricks answer is right, you need to inherit from Freezable.
The following Code gave me the same error as you got
Not working resource:
public class PrintBarcodesDocumentHelper : DependencyObject
{
public IEnumerable<BarcodeResult> Barcodes
{
get { return (IEnumerable<BarcodeResult>)GetValue(BarcodesProperty); }
set { SetValue(BarcodesProperty, value); }
}
public static readonly DependencyProperty BarcodesProperty =
DependencyProperty.Register("Barcodes", typeof(IEnumerable<BarcodeResult>), typeof(PrintBarcodesDocumentHelper), new PropertyMetadata(null, HandleBarcodesChanged));
private static void HandleBarcodesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Do stuff
}
}
Xaml:
<UserControl.Resources>
<Barcodes:PrintBarcodesDocumentHelper x:Key="docHelper" Barcodes="{Binding BarcodeResults}"/>
</UserControl.Resources>
My viewmodel is bound to the DataContext of the UserControl.
Error:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=BarcodeResults; DataItem=null; target element is 'PrintBarcodesDocumentHelper' (HashCode=55335902); target property is 'Barcodes' (type 'IEnumerable`1')
Working resource class:
public class PrintBarcodesDocumentHelper : Freezable
{
// Same properties
protected override Freezable CreateInstanceCore()
{
return new PrintBarcodesDocumentHelper();
}
}
Unfortunately i dont know why it have to be a Freezable.

In order to enable binding, GroupingProvider needs to be derived from Freezable or FrameworkElement or FrameworkContentElement and GroupValue needs to be a DependencyProperty.

Related

wpf attached property with collection doesnt get updated

I like to share a list between an application and a custom Usercontrol.
I use an IEnumerable as attached property to provide a list to a Listbox inside the custom UserControl. The ListBox then receives the attached property as ItemsSource. This works so far. But when the host list changes, the list inside the usercontrol should get updated. How can I achieve this ?
The current code sets the Usercontrol list, but when the host changes the list, the attached property won't get updated.
The host that uses the UserControl has a ComboBox, which should share its ItemsSource with the UserControl's ListBox
public ObservableCollection<Person> PersonList
{
get;
set;
}
The host's Xaml binds a ComboBox to the collection:
<ComboBox x:Name="combobox1" Width="200" ItemsSource="{Binding PersonList}" DisplayMemberPath="Name" SelectedIndex="0" IsEditable="True"></ComboBox>
The Usercontrol which is placed inside the host receives the collection via attached property. The binding looks heavy but seems ok:
<myUserCtrl:AdvEditBox
...
prop:DynamicListProvider.DynamicList="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},
Path=DataContext.PersonList}">
...
</myUserCtrl:AdvEditBox
The attached property has a callback, which currently gets called only once:
class DynamicListProvider : DependencyObject
{
public static readonly DependencyProperty DynamicListProperty = DependencyProperty.RegisterAttached(
"DynamicList",
typeof(IEnumerable),
typeof(DynamicListProvider),
new FrameworkPropertyMetadata(null, OnDynamicListPropertyChanged)));
public static IEnumerable GetDynamicList(UIElement target) {..}
public static void SetDynamicList(UIElement target, IEnumerable value) {..}
private static void OnDynamicListPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null && o is FrameworkElement)
{
...
}
}
The OnDynamicListPropertyChanged() should be called whenever the PersonList of the host changes. Do I have to put INotifyCollectionChanged inside the attached property? If so, where and how ?
here's my solution:
1) the usercontrol's dp:
public static readonly DependencyProperty SelectionListProperty = DependencyProperty.Register(
"SelectionList",
typeof(ObservableCollection<MyList>),
typeof(MyUserControl),
new UIPropertyMetadata(null));
(..add property get/set wrapper)
2) set your UserControls ItemsSource on the List, e.g.
_combobox.ItemsSource = SelectionList;
3) the host owns the list. Add the data and its property in the class which instantiates the usercontrol. In my case, I use readonly / oneway binding.
ObservableCollection<MyList> _bigList= new ObservableCollection<MyList>();
public ObservableCollection<MyList> BigList
{
get { return _bigList; }
}
4) set binding in xaml
<myctrl:MyUserControl
SelectionList="{Binding BigList, Mode=OneWay}"
...
/>
5) Now whenever you modify the _biglist, call your PropertyChangedEventHandler on "BigList". This will notify the UserControl's SelectionList as set by the binding and call the BigList get{}. Hope it's clear to you.

Binding a 'IsReadOnly' Dependency Property

I have tried to create a Dependency Property 'IsReadOnly' to automatically set all TextBoxes in my form to ReadOnly following certain events.
The property is set up in the code behind to my window with the textboxes and looks like:
public static readonly DependencyProperty IsReadOnlyProperty =
DependencyProperty.Register("IsReadOnly",
typeof(bool),
typeof(MainWindow),
new PropertyMetadata());
public bool IsReadOnly
{
get { return (bool)GetValue(IsReadOnlyProperty); }
set { SetValue(IsReadOnlyProperty, value); }
}
Xaml code for textboxes is similar to this:
<TextBox Text="{numBind:NumericFormatBinding Path=BudgetStatement.OpExpTotalByFunction}"
IsReadOnly="{Binding Path=IsReadOnly,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=Window},
Mode=TwoWay}"
Name="txtOpExpByProgram" />
but it doesn't work. I can still edit values in the textbox. I'm getting the following output error:
System.Windows.Data Error: 40 : BindingExpression path error: 'IsReadOnly' property not found on 'object' ''ListCollectionView' (HashCode=54963679)'. BindingExpression:Path=IsReadOnly; DataItem='ListCollectionView' (HashCode=54963679); target element is 'TextBox' (Name=''); target property is 'IsReadOnly' (type 'Boolean')
I don't know enough wpf to correctly understand this error, but it seems to have something to do with the ListCollectionView - but I haven't tried to attach the property to a ListCollectionView so I'm stuck.
Googling suggests it might be due to the DataContext and dependency properties needing special treatment (http://stackoverflow.com/questions/8497841/dependency-property-and-binding-error), or maybe the PropertyMetaData should be a Framework (or UI)PropertyMetaData.
Can anyone point me in the right direction to find out what isn't working?
tia
alex
ps: the numbind thing just sets the stringformat in all the text boxes
After reading the comment, Change the owner type from MainWindow to BudgetMainWindow.
public static readonly DependencyProperty IsReadOnlyProperty =
DependencyProperty.Register("IsReadOnly",
typeof(bool),
typeof(BudgetMainWindow),
new PropertyMetadata());

Why doesn't this Binding work

I have a 3rd party SplitButton control that exposes some DropDownContent and a boolean IsOpen dp to control whether the drop down content is shown or not.
In the case the DropDownContent is a StackPanel with several Buttons, each of which is bound to a command in the view model. In addition to executing that command, clicking the button needs to close the open DropDown content, which I am doing with the AttachedBehavior below.
But my binding, which simple needs to get a reference to the ancestor SplitButton control doesn't work. In the binding, you will note I am trying to Find the first Ancestor control of type SplitButton. I do see however that the debug info says ancestor level 1, so I changed the level to as high as 4, but still with an error.
Can someone see what the fix is?
binding error
System.Windows.Data Error: 4 : Cannot find source for binding with reference
'RelativeSource FindAncestor, AncestorType='Xceed.Wpf.Toolkit.SplitButton',
AncestorLevel='1''. BindingExpression:(no path); DataItem=null; target element is
'CloseDropDownContentBehavior' (HashCode=8896066); target property is 'DropDownButtonElement' (type 'SplitButton')
xaml
<DataTemplate x:Key="AddNewPartyTemplate">
<StackPanel HorizontalAlignment="Right" Margin="10">
<toolkit:SplitButton x:Name="theSplitButton" Content="{resx:Resx Subject_AddNewWithChoices}">
<toolkit:SplitButton.DropDownContent>
<StackPanel x:Name="theStackPanel">
<Button Content="{resx:Resx Person}" Command="{Binding AddNewPersonCommand}"
>
<i:Interaction.Behaviors>
<local:CloseDropDownContentBehavior
*** DropDownButtonElement="{Binding
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type toolkit:SplitButton}}}"/>
</i:Interaction.Behaviors>
</Button>
...
</StackPanel>
</toolkit:SplitButton.DropDownContent>
</toolkit:SplitButton>
</StackPanel>
</DataTemplate>
attached behavior
public class CloseDropDownContentBehavior : Behavior<ButtonBase>
{
private ButtonBase _button;
protected override void OnAttached()
{
_button = AssociatedObject;
_button.Click += OnPartyButtonClick;
}
protected override void OnDetaching()
{
_button.Click -= OnPartyButtonClick;
}
// **** the point of it all
void OnPartyButtonClick(object sender, RoutedEventArgs e) { DropDownButtonElement.IsOpen = false; }
public static readonly DependencyProperty DropDownButtonElementProperty =
DependencyProperty.Register("DropDownButtonElement",
typeof(SplitButton), typeof(CloseDropDownContentBehavior), new UIPropertyMetadata(null, OnDropDownElementChanged));
public DropDownButton DropDownButtonElement
{
get { return (DropDownButton)GetValue(DropDownButtonElementProperty); }
set { SetValue(DropDownButtonElementProperty, value); }
}
private static void OnDropDownElementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
}
}
Guessing it's because Interaction.Behaviors isn't part of the visual tree, so the binding won't find the ancestor. Have you tried simply:
DropDownElement="{Binding ElementName=theSplitButton}"
Update from comments: the solution in this case is to simply use x:Reference:
DropDownElement="{x:Reference theSplitButton}"
i dont know the SplitButton.DropDownContent but if its behave like a context menu the following answer might help: WPF context menu whose items are defined as data templates
this trick is to bind with RelativeSource Self or Type ContextMenu and then set the Path to PlacementTarget.DataContext.YourProperty

WPF custom control databinding

I'm new to the development of custom controls in WPF, but I tried to develop a single one to use in a application that I'm developing. This control is an autocomplete textbox. In this control, I have a DependencyProprety that has a list of possible entries so a person can choose from while entering the text
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource",typeof (IList<object>),typeof (AutoCompleteTextBox),new PropertyMetadata(null));
public IList<object> ItemsSource
{
get { return (IList<object>) GetValue(ItemsSourceProperty); }
set
{
SetValue(ItemsSourceProperty, value);
RaiseOnPropertyChanged("ItemsSource");
}
}
I use this control in a usercontrol and associate this control to a property in the viewmodel
<CustomControls:AutoCompleteTextBox Height="23" Width="200"
VerticalAlignment="Center" Text="{Binding Path=ArticleName, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding Path=Articles,
Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
</CustomControls:AutoCompleteTextBox>
I have a viewmodel that I assign on the usercontrol load to the datacontext of the usercontrol load
protected virtual void Window_Loaded(object sender, RoutedEventArgs e)
{
if (!DesignerProperties.GetIsInDesignMode(this))
{
this.DataContext = viewModel;
SetLabels();
}
}
This viewmodel has the property Articles with values but the ItemsSource property of the control is null when I try to search in the list after the user enter some text.
Is there any special step that I missed when I create the control so use the mvvm pattern.
I hope that the explain the problem in a understandable way. Any help/hints would be welcome.
There are two issues here:
First, you're dependency property is defining the "default" value for this property to be null. You can change that by changing the metadata to specify a new collection:
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource",typeof (IList<object>),typeof (AutoCompleteTextBox),
new PropertyMetadata(new List<object>));
Secondly, when using dependency properties, the setter can't contain any logic. You should keep your property set as:
public IList<object> ItemsSource
{
get { return (IList<object>) GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
This is because the setter doesn't actually get called by the binding system - only when you use code. However, since the class is a DependencyObject and this is a DP, you don't need to raise property changed events.

Custom control ContentProperty DataBinding

I'm running in a issue while trying to use dependency properties in objects which are parts of a collection, inside acustom control, collection identified with the "ContentProperty" attribute. Ok, that's quite unclear. Here is sample of my custom control :
Here is my custom control basic definition :
[ContentProperty("SmarSearchScopes ")]
public class SmartSearchCc : Control
{
List<SmartSearchScope> SmarSearchScopes {get;set;}
(more code here)
}
Here is the basic definition of a SmartSearchScope object :
public class SmartSearchScope : DependencyObject
{
public static readonly DependencyProperty ViewProperty =DependencyProperty.Register("View", typeof (ICollectionView), typeof (SmartSearchScope),new UIPropertyMetadata(null,OnViewChanged));
public static readonly DependencyProperty FilterColumnsProperty =DependencyProperty.Register("FilterColumns", typeof (IEnumerable<ColumnBase>), typeof (SmartSearchScope),new UIPropertyMetadata(null, OnFilterColumnsChanged));
public ICollectionView View
{
get { return (ICollectionView) GetValue(ViewProperty); }
set { SetValue(ViewProperty, value); }
}
public IEnumerable<ColumnBase> FilterColumns
{
get { return (IEnumerable<ColumnBase>) GetValue(FilterColumnsProperty); }
set { SetValue(FilterColumnsProperty, value); }
}
(more code here)
}
All that for what ? Being able to pass a collection of SmartSearchScope objects via XAML like so :
<SmartSearch:SmartSearchCc HorizontalAlignment="Stretch" Grid.Row="0" >
<SmartSearch:SmartSearchScope FilterColumns="{Binding ElementName=CcyPairsConfigBlotter, Path=Columns}" View ="{Binding ElementName=CcyPairsConfigBlotter, Path=ItemsSource}"/>
<SmartSearch:SmartSearchScope FilterColumns="{Binding ElementName=ClientConfigBlotter, Path=Columns}" View ="{Binding ElementName=ClientConfigBlotter, Path=ItemsSource}"/>
</SmartSearch:SmartSearchCc>
'ClientConfigBlotter' and 'CcyPairsConfigBlotter' are just two ItemsControls which expose a 'Columns' and an 'ItemSource' d-property.
The problem here is that althought my 2 SmartSearchScope objects gets instantiated, the databinding on the "View" and "FilterColumns" d-properties is not made and I never go througth the the associated callbacks.
In addition, here is the output error message I got when creating the custom control.
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Columns; DataItem=null; target element is 'SmartSearchScope' (HashCode=56862858); target property is 'FilterColumns' (type 'IEnumerable`1')
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=ItemsSource; DataItem=null; target element is 'SmartSearchScope' (HashCode=56862858); target property is 'View' (type 'ICollectionView')
This is obvious that I'm missing something but I can't find what.
I must say that, in a previous version of that control, these 2 problematic d-properties where SmartSearchCc properties and that all worked just fine.
Thanks for your help :)
--bruno
I had a similar problem here: Bindings on child dependency object of usercontrol not working
The reason the binding doesn't work is because DependencyObjects don't have a DataContext property. In my case I changed them to inherit from FrameworkElement which solved the problem.
Although as someone else has mentioned, changing the parent control to an ItemsControl could simplify things.
Ok, problem solved, I swithc inheritance of my main custom control from control to ItemsControl and inheritance of my child object to FrameWork element and that's it. no need to further modifications.
Thank you all for your suggestions !

Resources