Binding a ViewModel property to a property of a static resource - wpf

I have an interface IFooBar and some concrete implementations of it, FooBarOne and FooBarToo.
public interface IFooBar
{
int Value { get; set; }
}
public class FooBarOne : IFooBar { ... }
public class FooBarTwo : IFooBar { ... }
I've added a DependencyProperty (called FooBar) of type IFooBar to a custom control MyControl.
public static readonly DependencyProperty FooBarProperty = ...
public IFooBar FooBar
{
get { return (IFooBar)GetValue(FooBarProperty ); }
set { SetValue(FooBarProperty, value); }
}
Whenever this control is used, I can create instances of FooBarOne or FooBarTwo as static resources, and then use these to set the FooBar DependencyProperty on instances of MyControl and this all works as expected.
<UserControl.Resources>
<ResourceDictionary>
<ns:FooBarOne x:Key="MyFooBarOne" Value="1"/>
<ns:FooBarTwo x:Key="MyFooBarTwo" Value="2"/>
</ResourceDictionary>
</UserControl.Resources>
...
<controls:MyControl FooBar="{StaticResource MyFooBarOne}"/>
<controls:MyControl FooBar="{StaticResource MyFooBarTwo}"/>
What I'm struggling with is that I now need to bind a value from a ViewModel to the the IFooBar.Value property.
I tried adding the following to my resouces:
<UserControl.Resources>
<ResourceDictionary>
<ns:FooBarOne x:Key="MyFooBarOne" Value="{Binding SomeViewModelProperty}"/>
<ns:FooBarTwo x:Key="MyFooBarTwo" Value="{Binding SomeViewModelProperty}"/>
</ResourceDictionary>
</UserControl.Resources>
But this doesn't work because IFooBar.Value isn't a DependencyProperty.
I realise I could probably add a new DependencyProperty for IFooBar.Value to my MyControl, but in reality IFooBar actually contains numerous properties and I wanted to avoid having to create a new DependencyProperty for each of the properties on IFooBar.
Is there a way for me to bind a ViewModel property onto the the properties of my IFooBar instances?

Is there a way for me to bind a ViewModel property onto the the properties of my IFooBar instances?
No, for you to be able to bind something to a property in XAML, the target property must be a dependency property.
In the below sample markup, Value is the target property and SomeViewModelProperty is the source property:
<ns:FooBarOne x:Key="MyFooBarOne" Value="{Binding SomeViewModelProperty}"/>
Again, the target property must be a dependency property for you to be able to bind a value to it.

I presume that you're using MVVM, if so, then you should have properties on your ViewModel that return an IFooBar rather than declaring static resources. Then you can just return those values. If you need to specifically create an IFooBar for a given value, then you can create a ValueConverter that takes your value and spits out a FooBarOne or FooBarTwo.

Related

Handling properties of custom controls in MVVM (WPF)

I have a custom control which will have properties that can be set which will affect the logic of how the control is handled. How should this be handled in MVVM?
Currently I'm stuck trying to pass a DependencyProperty to the ViewModel.
Example code:
CustomControl.xaml
<UserControl x:Name="Root" ...>
<UserControl.DataContext>
<local:CustomControlViewModel SetDefaultValue="{Binding ElementName=Root, Path=SetDefaultValue, Mode=TwoWay}"/>
</UserControl.DataContext>
...
</UserControl>
CustomControl.xaml.cs
...
public static readonly DependencyProperty SetDefaultValueProperty = DependencyProperty
.Register("SetDefaultValue",
typeof(bool),
typeof(CustomControl),
new FrameworkPropertyMetadata(false));
public string SetDefaultValue
{
get { return (string)GetValue(SetDefaultValueProperty ); }
set { SetValue(SetDefaultValueProperty , value); }
}
...
CustomControlViewModel.cs
...
private bool setDefaultValue;
public bool SetDefaultValue
{
get { return setDefaultValue; }
set
{
if (setDefaultValue!= value)
{
setDefaultValue= value;
OnPropertyChanged("SetDefaultValue"); // INotifyPropertyChanged
}
}
}
...
My goal with this property specifically is to be able to set a default value (getting the default value requires running business logic). So in another view I would use this control like this:
<local:CustomControl SetDefaultValue="True"/>
(Before I answer I want to point out that what you have here is actually a user control, not a custom control. That's not nit-picking on my part; A user control is something derived from the UserControl class and it typically has an associated XAML file. A custom control just derives from the Control class and has no associated XAML file. A custom control requires you set to a control template. Custom controls can be styled. User controls cannot.)
The thing about UserControl is that sometimes we create one assuming one specific DataContext, of one type and then we make all of its XAML bind to that object type. This is good for big, main pages of an application that are not meant to be re-used in too many places
But another approach -- that you have started to do here -- is to give our user controls their own dependency properties. So in this case, why not dispense with the need for this control to have any specific DataContext altogether? This is the first step to making user controls truly re-usable in many places.
Unless this control is huge, there's a good chance that When you are laying out its XAML, it can get everything that XAML needs to bind to in just a few properties. So why not make all those properties into dependency properties and make the control's XAML bind to itself?
Make the class be its own DataContext. Set that property on the root UI element of the control's layout and then every Binding should work well.
To illustrate, I've renamed your control class MyUserControl I've renamed your "SetDefaultValue" property to just be "BoolOption" Let's assume that all it needs to show is a checkbox, representing the bool value and a string label on the checkbox. We can do this with just two dependency properties. (In effect, this entire control is now just a pointless, glorified CheckBox control but please ignore that)
MyUserControl.xaml.cs
public static readonly DependencyProperty BoolOptionProperty =
DependencyProperty.Register(nameof(BoolOption),
typeof(bool),
typeof(MyUserControl),
new FrameworkPropertyMetadata(false));
public string BoolOption
{
get { return (string)GetValue(BoolOptionProperty ); }
set { SetValue(BoolOptionProperty , value); }
}
public static readonly DependencyProperty CheckBoxLabelProperty =
DependencyProperty.Register(nameof(CheckBoxLabel),
typeof(string),
typeof(MyUserControl),
new FrameworkPropertyMetadata(string.Empty));
public string CheckBoxLabel
{
get { return (string)GetValue(CheckBoxLabelProperty ); }
set { SetValue(CheckBoxLabelProperty , value); }
}
// Constructor. Here we set the control to be its own UI's DataContext
public MyUserControl()
{
InitializeComponent();
// Make us be the UI's DataContext. Note that I've set the
// x:Name property root Grid in XAML to be "RootUiElement"
RootUiElement.DataContext = this;
}
MyUserControl.xaml
<UserControl x:Class="MyCompany.MyApp.MyUserControl"
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:MyCompany.MyApp.Controls"
x:Name="Root"
d:DesignHeight="450"
d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type local:MyUserControl}}"
mc:Ignorable="d">
<Grid x:Name="RootUiElement">
<CheckBox IsChecked="{Binding BoolOption}"
Content="{Binding CheckBoxLabel"
/>
</Grid>
</UserControl>
Finally you could use the control anywhere you wanted, no matter what your current DataContext is, like this
<local:MyUserControl BoolOption="True" CheckBoxLabel="Is Option A enabled?"/>
<local:MyUserControl BoolOption="False" CheckBoxLabel="Is Option B?"/>
Or even bind it to some other DataContext where you're using it like this. Suppose my current DataContext is a view-model that has a boolean UserBoolOptionC property
<local:MyUserControl BoolOption="{Binding UseBoolOptionC}" "Is Option C enabled?"/>

Value Update of Bound ViewModel Property Fails

I am trying to bind a custom control property to a property of its view model and its failing.
I have defined a Dependency property for settings StartDate and updated the PropertyChangeCallback method
public static readonly DependencyProperty StartDateProperty =
DependencyProperty.Register(StartDatePropertyName,
typeof(DateTime),
typeof(CustomDateTimeControl),
new PropertyMetadata(DateTime.Now.AddYears(-7),
OnStartDatePropertyChanged));
private static void OnStartDatePropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
DateTime dtNewValue = (DateTime)e.NewValue;
if (dtNewValue != DateTime.MinValue)
{
DateTimeControl dtCtrl = d as DateTimeControl;
dtCtrl.StartDate = (DateTime)e.NewValue;
dtCtrl.CoerceValue(StartDateProperty);
}
}
The StartDate property gets bound to its ViewModel's Start Date, since VM needs to perform some operation which would then be used to define the next available view for the custom control.
<Setter Property="StartDate"
Value="{Binding StartDate,
Mode=OneWayToSource,
UpdateSourceTrigger=PropertyChanged}" />
Also the DependencyProperty defined within is set from the mainWindow view
<CustomDateTimeLib:CustomDateTimeControl StartDate="01/01/2000 00:00:00" />
The binding updates the property in the view model only with the default value of the dependency property but not with the value being set with in the MainView as above even though the dependency property is getting updated with the value from MainView.
ViewModelLocator class
public class ViewModelLocator
{
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<CalendarViewModel>();
}
/// <summary>
/// Gets the Main property.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public CustomDateTimeLib:CustomDateTimeControl CalendarVM
{
get
{
return ServiceLocator.Current.GetInstance<CustomDateTimeControl>();
}
}
}
App.Xaml
<Application x:Class="MvvmCustomTestApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:CustomDateTimeLib.ViewModel"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ignore="http://www.galasoft.ch/ignore"
StartupUri="MainWindow.xaml"
mc:Ignorable="d ignore">
<Application.Resources>
<!--Global View Model Locator-->
<vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
</Application.Resources>
ApplyTemplate override method in CustomDateTimeControl class
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
CalendarViewModel vm = (CalendarViewModel)this.DataContext;
vm.StartDate = this.StartDate;
}
Also defined a property change callback method for StartDate
If I understand you correctly, you have a custom DP that you want to bind to a ViewModel property, however you also want to set the Default Value for this property from the View.
That is not an ideal setup for MVVM or for when you're using bindings. MVVM is supposed to have all the logic, including things like "default value for X" in the ViewModel layer, and the View layer is only used to provide the user with a visual way to interact with the ViewModel (data) layer.
So your solutions are either :
Set the default value in your ViewModel
Provide handling in your Dependency Property that if value == DateTime.Min, use a different default value
Use a Converter (for DateTime) or FallbackValue (for DateTime?) if you really want to have the View supply the default value
Use a second DP to define the default value that should be used
Add a Loaded event handler to the control to read the DataContext and set the Default Value
Option 1 is the best solution if you are using MVVM, since things like a custom DefaultValue should be set in the ViewModel, not the View.
Option 2 is best if this default value is specific to this UserControl, and will be the same anytime this control is used.
Options 3, 4, and 5 are for if you really do insist on setting the default value from the View layer for whatever reason. Which one to use depends on your situation.
Assuming you use #1, I would expect your final XAML to look like this :
<!-- assumes DataContext is of type DateTimeCtrlVM via inheritance or direct binding -->
<CustomDateTimeLib:CustomDateTimeControl StartDate="{Binding StartDate}" />
That's it.
That <Setter> in your XAML code above is actually causing the following to happen :
Control is created with default value of 1/1/2000
Property is bound OneWayToSource to VM property, which means data will only flow from Target (Control) to Source (ViewModel), so the value of 1/1/2000 is getting persisted to your VM
So get rid of that Setter which binds the property as OneWayToSource, use the XAML binding shown above, and set the default value in the ViewModel and it should work.

Custom markupextension on custom DataTemplate property

I'm wondering if there is a way to use a custom markup extension on a custom property on a type derived from datatemplate?
I need some additional information inside my DataTemplates:
public class MyDataTemplate : DataTemplate
{
public string GroupName { get; set; }
public string TemplateKey { get; set; }
public object Geometry { get; set; }
}
<MyDataTemplate x:Key="Foo" DataType="{x:Type system:String}" GroupName="Group1"
Geometry="{telerik:CommonShape ShapeType=RectangleShape}">
...
</MyDataTemplate>
When I put the CommonShape markupextension on the DataType property, everything works.
When I put the x:Type markupextension on the Geometry property, everything works, too.
But if I put the custom markupextension on the custom property, I get the error
The property 'Geometry' cannot be set as a property element on template.
Only Triggers and Storyboards are allowed as property elements.
Is there any workaround for this?
Alex
Edit:
One possible workaround could be to put the values of the markupextension into the resource dictionary and use a StaticResource on the Geometry property - however, I'm not sure if this is possible using xaml?

UserControl enum DependencyProperty not binding

I created a UserControl that contains 3 DependencyProperties. Two are working fine, but there is one that gives me a real headache.
I have an enum (outside the UserControl class but same namespace):
public enum RecordingType
{
NoRecording,
ContinuesRecording,
EventRecording
}
I created a DependencyProperty for it as follows:
public static DependencyProperty SelectedRecordingTypeProperty = DependencyProperty.Register("SelectedRecordingType", typeof(RecordingType), typeof(SchedulerControl),
new FrameworkPropertyMetadata((RecordingType)RecordingType.NoRecording, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public RecordingType SelectedRecordingType
{
get
{
return (RecordingType)GetValue(SelectedRecordingTypeProperty);
}
set
{
SetValue(SelectedRecordingTypeProperty, value);
}
}
and I'm using it in XAML like this:
<userControls:SchedulerControl
Grid.Row="1"
Grid.Column="3"
SelectedRecordingType="{Binding CurrentRecordingType,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
FullRecordingSchedule="{Binding MondayFullRecordingSchedule,UpdateSourceTrigger=PropertyChanged}"
SelectedRecordingTime="{Binding MondaySelectedRecordingTime,UpdateSourceTrigger=PropertyChanged}"/>
There are two more DependencyProperties that work just fine (I get to their get and set methods inside the UserControl), but this one is just a no-go. I created DPs before and I'm doing everything the same. I also made sure the binding in my VM is ok and the getter and setter are being called correctly.
Any help would be great!
Also I checked that I my VM. The binding does execute.
Let me show an other solution for UserControl (UC from now) with a ComboBox and Enum bindings.
Also a common problem, when you can bind the enum, but you can't get the SelectedItem of the ComboBox from the UC. This solution will also provide the SelectedItem.
For example, I have an ExampleUC : UserControl UC class, which is able to accept an enum, and to provide the SelectedItem. It will do it using properties (attributes in .xaml).
I also have an enum, called ExampleEnum, and the Window, which creates a new instance of ExampleUC and setting that's properties/attributes.
In the ExampleUC.xaml:
<UserControl x:Class="TestNamespace.View.ExampleUC"
xmlns:view="clr-namespace:TestNamespace.View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:markup="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid DataContext="{Binding RelativeSource={RelativeSource AncestorType={markup:Type view:ExampleUC}}}">
<ComboBox ItemsSource="{Binding EnumTypeArray}" SelectedItem="{Binding SelectedItem}"/>
</Grid>
</UserControl>
As you can see, the DataContext of the UC has been set to it's ancestor's DataContext, which means it can receive the wanted parameters (you can find more explanations about DataContext inheritance and visual tree, just make some researches about them).
The binded properties (EnumTypeArray and SelectedItem) are DependencyProperties in the ExampleUC.xaml.cs file:
public Array EnumTypeArray
{
get { return (Array)GetValue(EnumTypeArrayProperty); }
set { SetValue(EnumTypeArrayProperty, value); }
}
public object SelectedItem
{
get { return (object)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty EnumTypeArrayProperty =
DependencyProperty.Register("EnumTypeArray", typeof(Array), typeof(ExampleUC), new PropertyMetadata(new string[0]));
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(ExampleUC), new PropertyMetadata(null));
To create new DependencyProperty you can use the propdp code snippet. (Write it and press TAB by default). These properties will be shown as attributes in the .xaml editor, when you create and edit the instances of ExampleUC.
At this stage you have a UC, which can accept an enum, and return the SelectedItem.
The enum somewhere:
public enum ExampleEnum
{
Example1,
Example2
}
The Window, which uses the ExampleUC:
You have to add a new resource to the Window's resources, which will be an ObjectDataProvider in order to be able to use your enum as ItemsSource:
<Window.Resources>
<ObjectDataProvider x:Key="MyEnumName" MethodName="GetValues" ObjectType="{x:Type sys:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:ExampleEnum"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
Please note that, the local namespace prefix has been defined earlier at the namespaces' section, which is the namespace of ExampleEnum, for example:
xmlns:local="clr-namespace:TestNamespace.Data"
To use ExampleUC, in a Grid or Panel, use the following:
<views:ExampleUC EnumTypeArray="{Binding Source={StaticResource MyEnumName}}" SelectedItem="{Binding MyProperty, Mode=TwoWay}"/>
To set the Mode to TwoWay is necessary to be able to get and set the property.
Please note that, you might have to define the views namespace at the namespaces' section, if Visual Studio wouldn't do it for you.
As you can see, the previously defined DependencyProperties are showing up as attributes. The EnumTypeArray is responsible to fill the ComboBox's items, and the SelectedItem has been binded to MyProperty, which is a property in the model class, such as:
public ExampleEnum MyProperty{
get{ return _myProperty;}
set{
_myProperty = value;
OnPropertyChanged("MyProperty");
}
}
This example only shows how to use enums through UCs. Since this UC has only a single component (a ComboBox), it's useless in practice. If you decorate it with a Label or others, it would do the job.
Hope it helps.

Create StaticResource from DataContext?

All,
Is it possible to create a StaticResource from an object in DataContext (without added code-behind)? Take for example a DependencyProperty of a UserControl:
public static DependencyProperty ViewModelProperty = DependencyProperty.Register("ViewModel", typeof(IVMHeaderGeneric), typeof(UIHeader), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
public IVMHeaderGeneric ViewModel
{
get
{
return (IVMHeaderGeneric)this.GetValue(ViewModelProperty);
}
set
{
this.SetValue(ViewModelProperty, value);
}
}
IVMHeaderGeneric is an interface that is instantiated as a class by the consumer of this user control.
What I need to do is, somehow (preferably without code-behind), add this to the UserControl's Resources, thus allowing me to perform data-bindings on UIElements that do not inherit DataContext (i.e. the DataGridColumn comes to mind).
Thanks in advance.
I think that you could not create an instance the interface in the xaml resources, because, as you said, the implementation is out of UserControl scope.
Instead of creating StaticResource, you can use Binding to refer to the UserControl DataContext property. For example, if you give the name of your root element, Root, you can write the following:
<DataGridColumn SomeDependencyProperty="{Binding ElementName=Root, Path=ViewModel.Property}" />

Resources