Let's say I have a custom control which wraps another control (for example MyCustomButton). I expose a property Content, which wraps the inner control:
public object Content
{
get { return innerControl.Content; }
set { innerControl.Content = value; }
}
In order for a consumer to bind to this property, I need to define a DependencyProperty for it:
public static DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof (object), typeof (MyCustomButton));
but now I need my property definition to use GetValue/SetValue:
public object Content
{
get { return GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
so I'm not wrapping the value of the inner control anymore.
I can define PropertyMetadata to handle the PropertyChanged event of the DependencyProperty, but then I need a bunch of plumbing code to keep the values in sync and prevent infinite loopbacks on changed.
UPDATE: I can't just derive from Button because my UserControl has various other concerns.
Is there a better way to do this?
Well, depending on the particulars of why you're wrapping a button with a user control, you could define a custom control that inherits from button. Then, instead of wrapping the button and exposing the wrapped methods and properties that you want, you can simply override methods and properties whose behavior you want to define the custom control. This way, you'll get all of the functionality of button without the need to reinvent the wheel.
Here's a google link that walks you through it (one of the first that I found - there are plenty): http://knol.google.com/k/creating-custom-controls-with-c-net#
If the user control has other concerns, this may not be an option for you, but I'm offering this answer because the only purpose that you've mentioned for it is wrapping the button. I'd personally favor creating a custom control and inheriting rather than a user control and wrapping if the control in question is simply meant to be a more specific kind of wrapped/inherited control (i.e. button in your case).
Edit: In light of updated question...
You could do something along these lines. Here is the XAML of the client of your user control:
<Grid>
<local:MyControl ButtonContent="Click Me!"/>
</Grid>
</Window>
Here is the XAML for the user control itself:
<UserControl x:Class="GuiScratch.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:GuiScratch"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<StackPanel>
<ContentControl Content="Asdf"/>
<Button Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MyControl}},Path=ButtonContent}"/>
</StackPanel>
</Grid>
</UserControl>
And, here is the code behind:
public partial class MyControl : UserControl
{
public static readonly DependencyProperty ButtonContentProperty =
DependencyProperty.Register("ButtonContent", typeof(object), typeof(MyControl),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
public object ButtonContent
{
get { return (object)GetValue(ButtonContentProperty); }
set { SetValue(ButtonContentProperty, value); }
}
public MyControl()
{
InitializeComponent();
}
}
So, you don't need to handle the binding at all through code. Your client XAML binds to your dependency property, as does the XAML of the user control itself. In this fashion, they share the dependency property setting. I ran this in my little scratchpad, and the result is (at least my understanding of) what you're looking for. The main window displays the user control as a stack panel with the text "Asdf" and then a button with the text "Click Me!"
Related
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?"/>
I have a WPF application with two pages, now I wanted to navigate to the other page when the button in first the page is clicked (I wrote the command for button in the first page), but the logic should be through the viewmodel. How to achieve this?
When I write WPF applications that need to navigate to different pages, I like to follow Rachel Lim's method to implement it using DataTemplates and ViewModels. You can follow the link to her page to get the exact code for the solution, but I'll give a little summary of her method here.
In her method, she creates a ViewModel that represents the application and has a property called CurrentPage which holds a ViewModel. You can then create a command on the ApplicationViewModel called ChangePage. This command will take the ViewModel that is passed as a parameter and sets it to the CurrentPage.
The xaml takes the responsibility of switching out the correct views. When using this method, I put a ContentControl in my MainWindow and bind the Content property to ApplicationViewModel.CurrentPage. Then in the resources of the MainWindow, I create DataTemplates to tell the view "When I try to display this ViewModel, put that View on the screen".
You don't really provide any code. But I assume your Navigation is in your code behind. You could do this by binding a Command OneWayToSource.
XAML
<local:MainWindow x:Class="WpfNameSpace.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:WpfNameSpace"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d"
NavigatePageCommand="{Binding Path=MyViewModel.NavigateCommand, Mode=OneWayToSource}"
Title="MainWindow" Height="600" Width="800">
<Grid>
</Grid>
</local:MainWindow>
Please take a look at local:MainWindow.
C#
public partial class MainWindow : Window
{
public ICommand NavigatePageCommand
{
get { return (ICommand) GetValue(NavigatePageCommandProperty); }
set { SetValue(NavigatePageCommandProperty, value); }
}
// Using a DependencyProperty as the backing store for NavigatePageCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty NavigatePageCommandProperty =
DependencyProperty.Register("NavigatePageCommand", typeof(ICommand), typeof(MainWindow),
new PropertyMetadata(0));
public MainWindow()
{
InitializeComponent();
NavigatePageCommand = new RelayCommand(Navigate);
}
public void Navigate()
{
//Do Navigation here
}
}
I assume you are familiar with Commands, ViewModels and Bindings and you get the idea.
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.
I'm working on a Surface WPF project where we try to implement the MVVM pattern. Within this project we are building a few custom controls which we bind to different viewmodels.
For example we have a settings control which has a settings viewmodel and we have a mainviewmodel which is the "overall" viewmodel.
In our surfacewindows.xaml page we are setting the datacontext to the main viewmodel by using the viewmodel locator in mvvm-light. Also on our surfacewindow.xaml we have added our settings control and on the control we have set the datacontext to the settings viewmodel.
Now we need both viewmodels to interact with each other: The current case is that we need to set the visibility of the settings control. We have a property on the main viewmodel that is a boolean (IsSettingsControlVisible) which is bound to the controls Visibility property by using a converter to convert the boolean to a visibility object.
The problem arises now when we need to set the visibility to not visible by clicking on a close button on the settings control. Because we have set the datacontext on the control to the settings viewmodel, we cannot access the mainviewmodel.
What we have thought of until now is adding the settings viewmodel as a property to the mainviewmodel and remove the datacontext from the settings control. In the settingscontrol we will than use the binding as SettingsProperty.Property. Than we can access the mainviewmodel too from the setttings control. Does that make sense? Are there better ways of doing these kind of interactions?
I really like to hear your ideas about how to make these interactions happen.
I tend to work with graphs of view models that are constructed using Castle Windsor. The top level view model uses constructor injection to receive the next level of view models that it requires. And in the views I bind content presenters to properties on the view models to create the corresponding view graph.
Doing this, it's quite easy for parent child view models to communicate, but a bit harder for sibling or more distant view models to communicate.
In these instances, I tend to use an event aggregator, or Messenger to allow the view models to communicate.
As you are already using MVVMLight, I'd suggest using the MVVM Light toolkits Messenger system. It's intended for message exchange between ViewModels.
The concept behind is the Mediator pattern where different objects exchange information without knowing each other.
Here's an example:
In the SettingsViewModel register to an event that tells to show the settings dialog
public SettingsViewModel()
{
Messenger.Default.Register<ShowSettingsMessage>(this, ShowSettingsDialog);
}
private void ShowSettingsDialog(ShowSettingsMessage showSettingsMessage)
{
// Set the visibility:
this.IsVisible = showSettingsMessage.Content;
}
In your MainViewModel you send the notification, wrapped in a Message:
// make the settings visible, e.g. the button click command:
Messenger.Default.Send(new ShowSettingsMessage(true));
And here's the message:
// the message:
public class ShowSettingsMessage : GenericMessage<bool>
{
public ShowSettingsMessage(bool isVisible)
: base(isVisible)
{ }
}
I wouldn't recommend making the SettingsViewModel a property of the Mainviewmodel as you lose the possibility to use the SettingsViewModel in a different context or even remove/exchange it.
Try to create a Dependency Property on the Settings control called IsSettingControlVisible and bind it with the parent viewModel.
EDIT:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public int MyProperty
{
get { return (int)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(int), typeof(UserControl1), new UIPropertyMetadata(0));
}
and use it like this...
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:UserControl1 MyProperty="{Binding Path=ParentViewModelProperty, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" />
</Grid>
</Window>
I am learning Silverlight. In the process, I'm trying to build a custom user control. My ultimate goal is to be able to write the following statement in XAML:
<my:CustomControl>
<my:CustomControl.MainControl>
<Canvas><TextBlock Text="Hello!" /></Canvas>
</my:CustomControl.MainContent>
</my:CustomControl>
The content of the control will be wrapped in a custom border. Once again, this is just a learning exercise. To append my border, I have create the following UserControl:
<UserControl x:Class="CustomControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
<Grid x:Name="LayoutRoot">
<Border>
<!-- CustomControl Content -->
</Border>
</Grid>
</UserControl>
The code-behind for this file looks like the following:
public partial class CustomControl : UserControl
{
public UIElement MainContent
{
get { return (UIElement)GetValue(MainContentProperty); }
set { SetValue(MainContentProperty, value); }
}
public static readonly DependencyProperty MainContentProperty =
DependencyProperty.Register("MainContent", typeof(UIElement), typeof(CustomControl),
new PropertyMetadata(null));
public CustomControl()
{
InitializeComponent();
}
}
The thing I am having a problem with is getting the MainContent to appear in my CustomControl. I am confident that I am setting it properly, but I'm not sure how to display it. I really want it to be a DependencyProperty as well so I can take advantage of data binding and animations.
How do I get the MainContent to appear in the CustomControl? Thank you
First you need to wait until the rest of the control has been parsed so you need to hook the loaded event:-
public CustomControl()
{
InitializeComponent();
Loaded += new RoutedEventHandler(CustomControl_Loaded);
}
Then in the loaded event assign your MainControl property to the Child property of the border. To do that its best if you give your Border an x:Name which for now I'll simple call "border".
void CustomControl_Loaded(object sender, RoutedEventArgs e)
{
border.Child = MainControl;
}
That'll get you going. Of course you may need to deal with the MainControl property being changed dynamically so you need add a bool isLoaded field in your control and set that in the loaded event. When true your MainControl setter should assign the incoming value to the border.Child.
Things can start to get more complicated and in fact I don't recommend this approach to creating a custom control. For a better approach see Creating a New Control by Creating a ControlTemplate