I am very new to the WPF development. I am updating an existing application and there seems to be a MVVM framework implemented.
Now I have a user control(ChartView.xaml) which has a dependency property:
public partial class ChartView : UserControl, IDisposable
{
public static readonly DependencyProperty SceneProperty = DependencyProperty.Register(
"Scene",
typeof(IScene),
typeof(ChartView),
new FrameworkPropertyMetadata(
default(IScene),
FrameworkPropertyMetadataOptions.AffectsRender,
ChartChangedCallback));
public IScene Scene
{
get => (IScene)GetValue(SceneProperty);
set => SetValue(SceneProperty, value);
}
}
I want to bind this property to the viewModel and I was using following code in Xaml of ChartView.xaml to do so:
<local:ChartView
x:Name="ChartView"
Scene="{Binding Path=(viewModels:ChartViewModel.Scene)}"
>
But the problem is that this code is recurrently calling the constructor on the user control as I get stackOverflow exception in "InitializeComponent()" method. Even if I remove the scene binding from xaml then also exception is there. As soon as I add
<local:ChartView>
I start getting stack overflow error.
Can anyone point out the correct way to do it.
Thanks
You are getting a StackOverflowException because you are creating an instance of your UserControl class inside its XAML, like
<UserControl x:Class="YourNamespace:ChartView"
xmlns:local="clr-namespace:YourNamespace" ...>
<local:ChartView .../>
</UserControl>
You should obviously not do that. Instead, bind the UserControl's Scene property to a view model property when you use it, e.g. in your MainWindow:
<Window ...>
<Window.DataContext>
<local:ChartViewModel/>
</Window.DataContext>
<Grid>
<local:ChartView Scene="{Binding Scene}"/>
</Grid>
</Window>
You may also create a default Style for your UserControl (e.g. in App.xaml), that sets up the Binding:
<Application.Resources>
<Style TargetType="local:ChartView">
<Setter Property="Scene" Value="{Binding Scene}"/>
</Style>
</Application.Resources>
Related
I have a problem implementing MVVM with a usercontrols.
I have an MVVM based application.
In one of the view (which is a usercontrol) I have a menu on the left and content on the right. The content change depending on the menu.
I tried to implement the MVVM with a usercontrol, but i dont know how.
Here is what i tried but it didn't work :
<UserControl x:Class="PoS.Views.OptionsView"
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:PoS.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<DataTemplate x:Name="SettingsTemplate" DataType="{x:Type viewmodels:SettingsViewModel}">
<views:SettingsView DataContext="{Binding}" />
</DataTemplate>
</UserControl.Resources>
<Grid>
</Grid>
</UserControl>
I'll be honest, I think you need to rewind a bit and read a good book on MVVM before continuing. Gary McLean Hall's Pro WPF and Silverlight MVVM is a good place to start.
To answer your question, I'll assume that this user control is set up with its DataContext pointing to your MainViewModel. The content on the right needs a corresponding property in the main view model i.e. something like this:
private ViewModelBase _CurrentPage;
public ViewModelBase CurrentPage
{
get { return this._CurrentPage; }
set
{
if (this._CurrentPage != value)
{
this._CurrentPage = value;
RaisePropertyChanged(() => this.CurrentPage);
}
}
}
You then create a bunch of "pages" or something that inherit ViewModelBase i.e. Page1ViewModel, Page2ViewModel, SettingsViewModel etc. You then create a ContentControl and bind its content to that property:
<ContentControl Content="{Binding CurrentPage}" />
So now if your view model does something like CurrentPage = new SettingsViewModel() then the ContentControl will be populated with whatever you declared as the DataTemplate for that type (i.e. a control of type views:SettingsView). If you assign the property to something else then the SettingsView will be destroyed and replaced by whatever the DataTemplate for the new type is.
In your example above only SettingsViewModel/SettingsView will work, because that's all you've created a DataTemplate for; in order for this to work you need to create a separate DataTemplate for each ViewModel/View pair type you create.
[Edit]: I figured out how to do this on my own. I posted my solution in the hope that it will save someone else a few days of Googling. If you are a WPF guru, please look at my solution and let me know if there is a better / more elegant / more efficient way to do this. In particular, I am interested in knowing what I don't know... how is this solution going to screw me down the road? The problem really boils down to exposing inner control properties.
Problem:
I am creating some code to auto-generate a data-bound GUI in WPF for an XML file. I have an xsd file that can help me determine the node types, etc. Simple Key/Value elements are easy.
When I parse this element:
<Key>value</Key>
I can create a new 'KeyValueControl' and set the DataContext to this element. The KeyValueControl is defined as a UserControl and just has some simple bindings on it. It works great for any simple XElement.
The XAML inside this control looks like this:
<Label Content={Binding Path=Name} />
<TextBox Text={Binding Path=Value} />
The result is a line that has a label with the element name and a text box with the value that I can edit.
Now, there are times where I need to display lookup values instead of the actual value. I would like to create a 'KeyValueComboBox' similar to the above KeyValueControl but be able to specify (based on information in the file) the ItemsSource, DisplayMemberPath, and ValueMemberPath. The 'DisplayMemberPath' and 'ValueMemberPath' bindings would be the same as the KeyValueControl.
I don't know if a standard user control can handle this, or if I need to inherit from Selector.
The XAML in the control would look something like this:
<Label Content={Binding Path=Name} />
<ComboBox SelectedValue={Binding Path=Value}
ItemsSource={Binding [BOUND TO THE ItemsSource PROPERTY OF THIS CUSTOM CONTROL]
DisplayMemberPath={Binding [BOUND TO THE DisplayMemberPath OF THIS CUSTOM CONTROL]
SelectedValuePath={Binding [BOUND TO THE SelectedValuePath OF THIS CUSTOM CONTROL]/>
In my code, I would then do something like this (assuming that this node is a 'Thing' and needs to display a list of Things so the user can select the ID:
var myBoundComboBox = new KeyValueComboBox();
myBoundComboBox.ItemsSource = getThingsList();
myBoundComboBox.DisplayMemberPath = "ThingName";
myBoundComboBox.ValueMemberPath = "ThingID"
myBoundComboBox.DataContext = thisXElement;
...
myStackPanel.Children.Add(myBoundComboBox)
So my questions are:
1) Should I inherit my KeyValueComboBox from Control or Selector?
2) If I should inherit from Control, how do I expose the inner Combo Box's ItemsSource, DisplayMemberPath, and ValueMemberPath for binding?
3) If I need to inherit from Selector, can someone provide a small example of how I might get started with that? Again, I'm new to WPF so a nice, simple example would really help if that's the road I need to take.
I ended up figuring how how to do this on my own. I'm posting the answer here so that others can see a solution that works, and maybe a WPF guru will come by and show me a better/more elegant way to do this.
So, the answer ended up being #2. Exposing the inner properties turns out to be the right answer. Setting it up is actually pretty easy.. once you know how to do it. There aren't many complete examples of this (that I could find), so hopefully this one will help someone else that runs into this problem.
ComboBoxWithLabel.xaml.cs
The important thing in this file is the use of DependencyProperties. Note that all we're doing right now is just exposing the properties (LabelContent and ItemsSource). The XAML will take care of wiring the internal control's properties to these external properties.
namespace BoundComboBoxExample
{
/// <summary>
/// Interaction logic for ComboBoxWithLabel.xaml
/// </summary>
public partial class ComboBoxWithLabel : UserControl
{
// Declare ItemsSource and Register as an Owner of ComboBox.ItemsSource
// the ComboBoxWithLabel.xaml will bind the ComboBox.ItemsSource to this
// property
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
ComboBox.ItemsSourceProperty.AddOwner(typeof(ComboBoxWithLabel));
// Declare a new LabelContent property that can be bound as well
// The ComboBoxWithLable.xaml will bind the Label's content to this
public string LabelContent
{
get { return (string)GetValue(LabelContentProperty); }
set { SetValue(LabelContentProperty, value); }
}
public static readonly DependencyProperty LabelContentProperty =
DependencyProperty.Register("LabelContent", typeof(string), typeof(ComboBoxWithLabel));
public ComboBoxWithLabel()
{
InitializeComponent();
}
}
}
ComboBoxWithLabel.xaml
The XAML is pretty straightforward, with the exception of the bindings on the Label and the ComboBox ItemsSource. I found that the easiest way to get these bindings right is to declare the properties in the .cs file (as above) and then use the VS2010 designer to setup the binding source from the properties pane. Essentially, this is the only way I know of to bind an inner control's properties to the base control. If there's a better way to do it, please let me know.
<UserControl x:Class="BoundComboBoxExample.ComboBoxWithLabel"
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"
mc:Ignorable="d"
d:DesignHeight="28" d:DesignWidth="453" xmlns:my="clr-namespace:BoundComboBoxExample">
<Grid>
<DockPanel LastChildFill="True">
<!-- This will bind the Content property on the label to the 'LabelContent'
property on this control-->
<Label Content="{Binding Path=LabelContent,
RelativeSource={RelativeSource FindAncestor,
AncestorType=my:ComboBoxWithLabel,
AncestorLevel=1}}"
Width="100"
HorizontalAlignment="Left"/>
<!-- This will bind the ItemsSource of the ComboBox to this
control's ItemsSource property -->
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType=my:ComboBoxWithLabel,
AncestorLevel=1},
Path=ItemsSource}"></ComboBox>
<!-- you can do the same thing with SelectedValuePath,
DisplayMemberPath, etc, but this illustrates the technique -->
</DockPanel>
</Grid>
</UserControl>
MainWindow.xaml
The XAML to use this is not interesting at all.. which is exactly what I wanted. You can set the ItemsSource and the LabelContent via all the standard WPF techniques.
<Window x:Class="BoundComboBoxExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="86" Width="464" xmlns:my="clr-namespace:BoundComboBoxExample"
Loaded="Window_Loaded">
<Window.Resources>
<ObjectDataProvider x:Key="LookupValues" />
</Window.Resources>
<Grid>
<my:ComboBoxWithLabel LabelContent="Foo"
ItemsSource="{Binding Source={StaticResource LookupValues}}"
HorizontalAlignment="Left"
Margin="12,12,0,0"
x:Name="comboBoxWithLabel1"
VerticalAlignment="Top"
Height="23"
Width="418" />
</Grid>
</Window>
For Completeness Sake, here is the MainWindow.xaml.cs
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
((ObjectDataProvider)FindResource("LookupValues")).ObjectInstance =
(from i in Enumerable.Range(0, 5)
select string.Format("Bar {0}", i)).ToArray();
}
}
I tried your solution but it fails for me. It does not pass the value over to inner control at all. What I did is declaration of same dependency properties in outer control and bound inner to outer like that:
// Declare IsReadOnly property and Register as an Owner of TimePicker (base InputBase).IsReadOnly the TimePickerEx.xaml will bind the TimePicker.IsReadOnly to this property
// does not work: public static readonly DependencyProperty IsReadOnlyProperty = InputBase.IsReadOnlyProperty.AddOwner(typeof(TimePickerEx));
public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register("IsReadOnly", typeof (bool), typeof (TimePickerEx), new PropertyMetadata(default(bool)));
public bool IsReadOnly
{
get { return (bool) GetValue(IsReadOnlyProperty); }
set { SetValue(IsReadOnlyProperty, value); }
}
Than in xaml:
<UserControl x:Class="CBRControls.TimePickerEx" x:Name="TimePickerExControl"
...
>
<xctk:TimePicker x:Name="Picker"
IsReadOnly="{Binding ElementName=TimePickerExControl, Path=IsReadOnly}"
...
/>
</UserControl>
EDIT: Set the View Model as the DataContext
There are a lot of questions on SO here but they all revolve around doing some funky CodeBehind stuff. What I want is:
I have a view model like this:
public class FooViewModel : INotifyPropertyChanged {
public Foo Foo {
get { ...}
set { ... }
}
}
And I define this in my UserControl like this:
<UserControl.DataContext>
<vm:FooViewModel />
</UserControl.DataContext>
In my CodeBehind file I defined a DependencyProperty:
public Foo Foo {
get { return (Foo)GetValue(FooProperty); }
set { SetValue(FooProperty, value); }
}
// Using a DependencyProperty as the backing store for Foo. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FooProperty =
DependencyProperty.Register("Foo", typeof(Foo), typeof(FooControl), new PropertyMetadata(null));
So I now can bind the Foo property when I use the control elsewhere.
The missing link is: How do I bind the UserControl's Foo to the ViewModel's Foo?
Currently, I am using some elaborate event handling in the CodeBehind to synchronize these, but I reckon there must be a way to do this in XAML, or is there not?
If you really want to do that - you can use a style (I'm using your original code when view model was in resources):
<UserControl.Resources>
<local:ViewModel x:Key="ViewModel" />
<Style TargetType="local:MyUserControl">
<Setter Property="Foo"
Value="{Binding Source={StaticResource ViewModel}, Path=Foo}" />
</Style>
</UserControl.Resources>
However having view model for a UserControl is not a good idea. Use dependency properties and bind to them directly from controls inside your UserControl. Then when you use UserControl - you will be able to bind its dependency properties from outside. However binding them from the inside (to separate view model) is usually not a good idea.
So in Caliburn Micro, I have been using the following method to compose a view inside of another view:
Put a ContentControl inside the composing View.
Create a property on the composing ViewModel, and assign to it the composed ViewModel
Give the ContentControl a x:Name attribute that matches the name of the composed ViewModel property on the composing ViewModel.
like so...
View:
<UserControl x:Class="MyProject.MyComposingView"
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">
<ContentControl x:Name="MyComposedViewModel"/>
</UserControl>
ViewModel:
class ComposingViewModel : PropertyChangedBase
{
private ComposedViewModel _myComposedViewModel;
public ComposedViewModel MyComposedViewModel
{
get { return _myComposedViewModel; }
set
{
_myComposedViewModel= value;
NotifyOfPropertyChange(() => Page);
}
}
public ComposingViewModel(ComposedViewModel myComposedViewModel)
{
MyComposedViewModel = myComposedViewModel;
}
}
Caliburn Micro automagically figures out that because it's a ContentControl it obviously doesn't want to bind to a ViewModel, but rather to its associated View, and so it does something under the hood to bind the ContentControl's Content property to MyComposedView instead of MyComposedViewModel.
But, what if I don't want to use a ContentControl? Like, maybe some reusable custom component of mine that wraps a ContentControl instead? For example:
<UserControl x:Class="MyProject.MyContentWrapper"
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"
mc:Ignorable="d">
<Grid x:Name="PreviewBox" SizeChanged="onSizeChanged">
<Image x:Name="BGImage" Source="{Binding BGImage}"/>
<ContentControl Content="{Binding}"/>
</Grid>
</UserControl>
If I replace the ContentControl with a MyContentWrapper, CaliburnMicro no longer works its magic to supply MyComposedView, and I end up with a TextBlock that says, MyProject.MyComposedViewModel.
How can I get CaliburnMicro to know this is a situation where it should supply the View rather than the ViewModel?
What you want to do is add a convention for your custom control:
Go to the code for ConventionMananger on github.
Search for AddElementConvention<ContentControl>.
Create a new method in your Bootstrapper that runs when your application starts. Add a call to ConventionManager.AddElementConvention<YourControl> similar to the one for ContentControl.
Make sure to put a ContentPropertyAttribute on your control and specify the content property.
Disclaimer: I'm on mobile and can't validate this.
I like to bind a custom property (owner Window) to my datacontext. How to do these in xaml.
I can not access on these property because my class is window and not MyView, its Window <Window x:Class="MyNamespace.MyView"
By changing my xaml to MyView class i get some errors that my class needs a inheritance from window.
Codebehind:
DependencyProperty MyValueProperty, Property MyValue
Xaml:
Bind MyValue to my datacontext.
I want these
If i try to change the class name inside xaml:
I get these error
you can instantiate a control that is
<local:MyView xmlns:local="YourNameSpaceToMyView" this way you can use your DP
You can set your DataContext to the current instance of your window class like this:
<Window x:Class="MyNamespace.MyView"
DataContext="{Binding RelativeSource={RelativeSource self}}">
Then you can access the underlying properties easily.
To bind to your custom property, you can do something like this:
DataContext="{Binding RelativeSource={RelativeSource self}, Path=MyProperty}"