I have a MVVM application with Window that should display content based on the child view model. I use ContentControl and data templates and this works fine:
DialogWindow.xaml
<Window x:Class="ContentControlApplication.DialogWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ContentControlApplication"
Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:TestViewModel}" >
<local:TestControl DataContext="{Binding}"/>
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding SubViewModel}" x:Name="myPresenter" />
</Window>
DialogWindow.xaml.cs
public partial class DialogWindow : Window
{
public DialogWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
MainViewModel.cs
public class MainViewModel
{
public object SubViewModel
{
get;
set;
}
public MainViewModel()
{
SubViewModel = new TestViewModel();
}
}
TestViewModel.cs
public class TestViewModel
{
public string TestProperty
{
get;
set;
}
public TestViewModel()
{
TestProperty = "_TEST_";
}
}
TestControl.xaml
<UserControl x:Class="ContentControlApplication.TestControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ContentControlApplication">
<Grid>
<TextBlock Text="{Binding Path=TestProperty}" FontSize="20" Margin="10" Width="100" Height="30" />
</Grid>
</UserControl>
Now I would like to specify the Window title in the child view, so I registered attached property in DialogWindow code-behind
public static readonly DependencyProperty DialogTitleProperty = DependencyProperty.RegisterAttached(
"DialogTitle",
typeof(string),
typeof(DialogWindow));
public static void SetDialogTitle(UIElement element, string value)
{
element.SetValue(DialogTitleProperty, value);
}
public static string GetDialogTitle(UIElement element)
{
return (string)element.GetValue(DialogTitleProperty);
}
and applied it to the child view
<UserControl ...
local:DialogWindow.DialogTitle="My Title">
...
</UserControl>
and tried to bind the Window.Title
<Window ....
Title='{Binding Path=Content.(local:DialogWindow.DialogTitle), ElementName="myPresenter"}'>
...
</Window>
But it does not work, since the content of the ContentControl is the view model itself and not the corresponding view as defined by the DataTemplate.
Exception thrown: 'System.InvalidCastException' in PresentationFramework.dll System.Windows.Data Error: 17 : Cannot get 'DialogTitle' value (type 'String') from 'Content' (type 'TestViewModel'). BindingExpression:Path=Content.(0); DataItem='ContentControl' (Name='myPresenter'); target element is 'DialogWindow' (Name=''); target property is 'Title' (type 'String') InvalidCastException:'System.InvalidCastException: Cannot cast from ContentControlApplication.TestViewModel to System.Windows.DependencyObject.'
How can I bind to the property attached to the view that is created on-the-fly from the data template?
Edited: Renamed MainWindow to DialogWindow to clear the example intent, corrected name of the attached property member field to follow guidelines.
Related
I am struggling to get a user control to accept a property from my Data Context object. I don't want to pass just the value; but the instance of the property because I would like to have converters operate on the attributes of the property.
I am very new to the WPF space, I've read many articles and none of them don't address this issue. The reason I'm trying to do this is because I have a calculations class that has many properties that need to be displayed and I don't really want to create a user control for each property or have 2,000 lines of repetitious XAML.
Any insight would be greatly appreciated.
Example Class
public class MyClass
{
[MyAttribute("someValue")]
public string Foo { get; set; }
}
View Model
public class MyViewModel : INotifyPropertyChanged
{
private _myClass;
public MyClass MyClass1
{
get => _myClass;
set
{
if(_myClass != value)
{
_myClass = value;
OnPropertyChanged();
}
}
}
}
Parent XAML
<UserControl DataContext="MyViewModel">
<Grid>
<!-- this is where I'm struggling, I think -->
<uc:MyConsumerControl ObjectProp="{Binding Path=MyClass1.Foo}"/>
</Grid>
</UserControl>
User Control
XAML
<UserControl DataContext={Binding RelativeSource={RelativeSource Mode=Self}}>
<Grid>
<TextBox Text="{Binding ObjectProp}"/>
<TextBlock Text="{Binding Path=ObjectProp, Converter={StaticResource MyAttrConverter}}"/>
</Grid>
</UserControl>
C#
public class MyConsumer : UserControl
{
public MyConsumer { InitializeComponent(); }
public object ObjectProp
{
get => (object)GetValue(ObjDepProp);
set => SetValue(ObjDepProp, value);
}
public static readonly DependencyProperty ObjDepProp =
DependencyProperty.Register(nameof(ObjectProp),
typeof(object), typeof(MyConsumer));
}
First of all, there is a naming convention for identifier fields of dependency properties:
public static readonly DependencyProperty ObjectPropProperty =
DependencyProperty.Register(nameof(ObjectProp), typeof(object), typeof(MyConsumer));
public object ObjectProp
{
get => GetValue(ObjectPropProperty);
set => SetValue(ObjectPropProperty, value);
}
Second, a UserControl that exposes bindable properties must never set its own DataContext, so this is wrong:
<UserControl DataContext={Binding RelativeSource={RelativeSource Mode=Self}}>
The XAML should look like this:
<UserControl ...>
<Grid>
<TextBox Text="{Binding ObjectProp,
RelativeSource={RelativeSource AncestorType=UserControl}}" />
<TextBlock Text="{Binding Path=ObjectProp,
RelativeSource={RelativeSource AncestorType=UserControl}, />
Converter={StaticResource MyAttrConverter}}"
</Grid>
</UserControl>
Finally, this is also wrong, because it only assigns a string to the DataContext:
<UserControl DataContext="MyViewModel">
It could probably look like shown below - although that would again explicitly set the DataContext of a UserControl, but perhaps one that could be considered a top-level view element like a Window or Page.
<UserControl ...>
<UserControl.DataContext>
<local:MyViewModel/>
</UserControl.DataContext>
<Grid>
<uc:MyConsumerControl ObjectProp={Binding Path=MyClass1.Foo}
</Grid>
</UserControl>
I have a usercontrol with couple of controls inside. So I decide to use ViewModel to do managing for all those bindable value. But I find my binding is always null. So how to setup binding for ViewModel in usercontrol
MainWindows.xaml
<Window x:Class="Test.MainWindow"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<cus:Wizard WizardModel="{Binding MyModel}"/>
</StackPanel>
</Window>
MainWindows.xaml.cs
public partial class MainWindow : Window
{
private ViewModel vm = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = vm;
}
}
ViewModel.cs(MainWindow viewmodel)
public class ViewModel : INotifyPropertyChanged
{
private Model _MyModel;
public Model MyModel
{
get
{
return _MyModel;
}
set
{
_MyModel = value;
NotifyPropertyChanged("MyModel");
}
}
}
Wizard.xaml(my UserControl)
<UserControl mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Something}" />
</Grid>
</UserControl>
Wizard.xaml.cs
public partial class Wizard : UserControl
{
private readonly object modelLock = new object();
private Model CurrentModel = new Model();
public Wizard()
{
InitializeComponent();
DataContext = CurrentModel;
}
public Model WizardModel
{
get { return (Model)this.GetValue(WizardModelProperty); }
set { this.SetValue(WizardModelProperty, value); }
}
public static readonly DependencyProperty WizardModelProperty = DependencyProperty.Register("WizardModel", typeof(Model), typeof(Wizard), new PropertyMetadata(null, new PropertyChangedCallback(ModelChanged)));
private static void ModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((Wizard)d).OnModelChanged();
}
private void OnModelChanged()
{
lock (this.modelLock)
{
if(CurrentModel != null)
{
CurrentModel = null;
}
if (WizardModel != null)
{
CurrentModel = WizardModel;
}
}
}
}
The WizardModel in UserControl is always null. So how to setup this ViewModel in UserControl
A UserControl that is supposed to operate on a particular view model class - or more precisely on a class with a particular set of public properties - may directly bind to the view model properties in its XAML.
Given a view model like
public class Model
{
public string Something { get; set; }
}
you may write a UserControl with nothing more than this XAML
<UserControl ...>
...
<TextBox Text="{Binding Something}" />
...
</UserControl>
and this code behind
public partial class Wizard : UserControl
{
public Wizard()
{
InitializeComponent();
}
}
If you now set its DataContext to an instance of Model (or any other class with a Something property), it will just work:
<local:Wizard DataContext="{Binding MyModel}"/>
Since the value of the DataContext property is inherited from parent to child elements, this will also work:
<StackPanel DataContext="{Binding MyModel}">
<local:Wizard/>
</StackPanel>
However, the UserControl still dependends on the existence of a Something property in its DataContext. In order to get rid of this dependence, your control may expose a dependency property
public static readonly DependencyProperty MyTextProperty =
DependencyProperty.Register(nameof(MyText), typeof(string), typeof(Wizard));
public string MyText
{
get { return (string)GetValue(MyTextProperty); }
set { SetValue(MyTextProperty, value); }
}
and bind the element in its XAML to its own property
<UserControl ...>
...
<TextBox Text="{Binding MyText,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
...
</UserControl>
Now you would bind the control's property instead of setting its DataContext:
<local:Wizard MyText="{Binding MyModel.Something, Mode=TwoWay}"/>
Following up on my previous question (Change brushes based on ViewModel property)
In my UserControl I have have a DependencyObject. I want to bind that object to a property of my ViewModel. In this case a CarViewModel, property name is Status and returns an enum value.
public partial class CarView : UserControl
{
public CarStatus Status
{
get { return (CarStatus)GetValue(CarStatusProperty); }
set { SetValue(CarStatusProperty, value); }
}
public static readonly DependencyProperty CarStatusProperty =
DependencyProperty.Register("Status", typeof(CarStatus), typeof(CarView), new PropertyMetadata(OnStatusChanged));
private static void OnStatusChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var control = (CarView)obj;
control.LoadThemeResources((CarStatus)e.NewValue == CarStatus.Sold);
}
public void LoadThemeResources(bool isSold)
{
// change some brushes
}
}
<UserControl x:Class="MySolution.Views.CarView"
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:views="clr-MySolution.Views"
mc:Ignorable="d"
views:CarView.Status="{Binding Status}">
<UserControl.Resources>
</UserControl.Resources>
<Grid>
<TextBlock Text="{Binding Brand}"FontSize="22" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
<UserControl
Where do I need to specify this binding? In the root of the UserControl it gives an error:
The attachable property 'Status' was not found in type 'CarView'
In my MainWindow I bind the CarView using a ContentControl:
<ContentControl
Content="{Binding CurrentCar}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type viewmodel:CarViewModel}">
<views:CarView />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
My ViewModel:
[ImplementPropertyChanged]
public class CarViewModel
{
public Car Car { get; private set; }
public CarStatus Status
{
get
{
if (_sold) return CarStatus.Sold;
return CarStatus.NotSold;
}
}
}
your binding isn't well written. instead of writing views:CarView.Status="{Binding Status}" you should write only Status="{Binding Status}"
It seems that your Control is binding to itself.
Status is looked for in CarView.
You should have a line of code in your control CodeBehind like :
this.DataContext = new ViewModelObjectWithStatusPropertyToBindFrom();
Regards
I have custom user control with the only property - SubHeader.
<UserControl x:Class="ExpensesManager.TopSection"
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"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
mc:Ignorable="d">
<StackPanel>
<Label Name="Header" Content="Constant header text" Style="{StaticResource Header}"/>
<Label Name="SubHeader" Content="{Binding SubHeaderText}" Style="{StaticResource SubHeader}"/>
</StackPanel>
public partial class TopSection : UserControl
{
public TopSection()
{
this.InitializeComponent();
}
public static readonly DependencyProperty SubHeaderTextProperty =
DependencyProperty.Register("SubHeaderText", typeof(string), typeof(TopSection));
public string SubHeaderText
{
get { return (string)GetValue(SubHeaderTextProperty); }
set { SetValue(SubHeaderTextProperty, value); }
}
}
There are two usages in xaml. First with the constant text:
...
<my:TopSection SubHeaderText="Constant text"/>
...
Another one using binding:
<Page x:Class="MyNamespace.MyPage"
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:my="clr-namespace:My"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
...
<my:TopSection SubHeaderText="{Binding MyModel.SubHeaderText}"/>
...
</Page>
My page code behind:
public partial class MyPage : Page
{
private MyModel myModel;
public MyModel MyModel
{
get
{
return this.myModel?? (this.myModel = new MyModel());
}
}
public MyPage(MyEntity entity)
{
this.InitializeComponent();
this.MyModel.MyEntity = entity;
}
}
MyModel code:
public class MyModel : NotificationObject
{
private MyEntity myEntity;
private string subHeaderText;
public MyEntity MyEntity
{
get
{
return this.myEntity;
}
set
{
if (this.myEntity!= value)
{
this.myEntity= value;
this.RaisePropertyChanged(() => this.MyEntity);
this.RaisePropertyChanged(() => this.SubHeaderText);
}
}
}
public string SubHeaderText
{
get
{
return string.Format("Name is {0}.", this.myEntity.Name);
}
}
}
The problem is that second one doesn't work. If I pass the constant text - it is displayed, if I use binding to the other property - nothing is displayed.
Does anybody knows what's wrong with the code? Thanks.
The problem is you set DataContext on the UserControl element. It will cause the following binding
<my:TopSection SubHeaderText="{Binding MyModel.SubHeaderText}"/>
to be relative to that DataContext, which is UserControl itself - so it cannot find the value.
To fix this, I suggest you not set DataContext on the UserControl, but the StackPanel inside:
<UserControl x:Class="ExpensesManager.TopSection"
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">
<StackPanel DataContext="{Binding RelativeSource={RelativeSource AncesterType=UserControl}}">
<Label Name="Header" Content="Constant header text" Style="{StaticResource Header}"/>
<Label Name="SubHeader" Content="{Binding SubHeaderText}" Style="{StaticResource SubHeader}"/>
</StackPanel>
Many people set DataContext on UserControl but that's really BAD. When you use the UserControl later, you have no idea the DataContext is actually set internally and will not respect the outside DataContext - really confusing. This rule also applies to other properties.
MyModel is a property in your DataContext? Try to check what object is your DataContext. If your data context is an object of your class MyModel you doesn't need the MyModel. part in your binding.
This kind of bindings always are to objects in your data context.
Hope this tips helps.
Declare your UserControl like this:
<my:TopSection
x:Name="myControl">
Then change your binding to this:
<my:TopSection SubHeaderText="{Binding MyModel.SubHeaderText, ElementName=myControl}"/>
You didn't set the Model in your UserControl
public partial class TopSection : UserControl
{
public class SampleViewModel { get; set; }
public TopSection()
{
this.InitializeComponent();
this.DataContext = new SampleViewModel();
}
public static readonly DependencyProperty SubHeaderTextProperty =
DependencyProperty.Register("SubHeaderText", typeof(string), typeof(TopSection));
public string SubHeaderText
{
get { return (string)GetValue(SubHeaderTextProperty); }
set { SetValue(SubHeaderTextProperty, value); }
}
}
Update
Since you don't want Model to known to the View. Create a ViewModel
public class SampleViewModel : NotificationObject
{
public class MyModel { get; set; }
public class SampleViewModel()
{
MyModel = new MyModel() { SubHeaderText = "Sample" };
RaisePropertyChange("MyModel");
}
}
I've got some problem I need some help with. I want to bind the visibility properties from a view model to the xaml elements so I get some visually changes (collapse or show in this case) by just changing the value in the viewmodel.
I got this xaml
<Window x:Class="PampelMuse.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:welcome="clr-namespace:PampelMuse.Views.Welcome"
xmlns:backend="clr-namespace:PampelMuse.Views.Backend"
xmlns:pampelMuse="clr-namespace:PampelMuse" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
Title="PampelMuse" Height="670" Width="864">
<Grid>
<Image HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Source="Resources/Images/Backgrounds/4.jpg" Stretch="UniformToFill" />
<welcome:WelcomeScreen x:Name="UIWelcome" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="{Binding ElementName=UiWelcomeVisibility}" />
<backend:BackendUI x:Name="UIBackend" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Visibility="{Binding ElementName=UiBackendVisibility}" />
</Grid>
The visibilities as you can see are binded to the properties UiWelcomeVisibility and UiBackendVisibility in the UIModel. These properties are now defined as followed:
public partial class MainWindow : Window
{
private ViewModel.ViewModel ViewModel = PampelMuse.ViewModel.ViewModel.GetInstance();
public MainWindow()
{
InitializeComponent();
DataContext = ViewModel; // Setting the data context what effects all the xaml elements in this component too, including UIWelcome and BackendUI
ViewModel.UIModel.UiBackendVisibility = Visibility.Collapsed;
}
The ViewModel:
public class ViewModel
{
private static ViewModel instance = new ViewModel();
public UIModel UIModel = UIModel.GetInstance();
public static ViewModel GetInstance()
{
return instance;
}
}
And the UIModel:
public class UIModel
{
private static UIModel instance = new UIModel();
public Visibility UiWelcomeVisibility { get; set; }
public Visibility UiBackendVisibility { get; set; }
public static UIModel GetInstance()
{
return instance;
}
}
I just don't see any coding mistakes here (and I don't get some at runtime in fact) but the BackendUI-visibility-property is not changed by the UiBackendVisibility of UIModel.
Any ideas? Thanks so far.
You are doing the binding wrong. Visibility="{Binding ElementName=UiWelcomeVisibility}" sets the visibility of an element equal to another visual element named "UiWelcomeVisibility". There are two problems with this:
There is no element named "UiWelcomeVisibility" in the first place.
Even if there were, a visual element itself is not a valid value for the Visibility property.
What you want is to databind to the viewmodel instead. Assuming that you have already set the DataContext to the viewmodel, just use
<welcome:WelcomeScreen ... Visibility="{Binding UiWelcomeVisibility}" />