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
Related
I've spent some time trying to solve this problem but couldn't find a solution.
I am trying to bind commands and data inside an user control to my view model. The user control is located inside a window for navigation purposes.
For simplicity I don't want to work with Code-Behind (unless it is unavoidable) and pass all events of the buttons via the ViewModel directly to the controller. Therefore code-behind is unchanged everywhere.
The problem is that any binding I do in the UserControl is ignored.
So the corresponding controller method is never called for the command binding and the data is not displayed in the view for the data binding. And this although the DataContext is set in the controllers.
Interestingly, if I make the view a Window instead of a UserControl and call it initially, everything works.
Does anyone have an idea what the problem could be?
Window.xaml (shortened)
<Window x:Class="Client.Views.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:Client.Views"
mc:Ignorable="d">
<Window.Resources>
<local:SubmoduleSelector x:Key="TemplateSelector" />
</Window.Resources>
<Grid>
<StackPanel>
<Button Command="{Binding OpenUserControlCommand}"/>
</StackPanel>
<ContentControl Content="{Binding ActiveViewModel}" ContentTemplateSelector="{StaticResource TemplateSelector}">
<ContentControl.Resources>
<DataTemplate x:Key="userControlTemplate">
<local:UserControl />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</Grid>
</Window>
MainWindowViewModel (shortened)
namespace Client.ViewModels
{
public class MainWindowViewModel : ViewModelBase
{
private ViewModelBase mActiveViewModel;
public ICommand OpenUserControlCommand { get; set; }
public ViewModelBase ActiveViewModel
{
get { return mActiveViewModel; }
set
{
if (mActiveViewModel == value)
return;
mActiveViewModel = value;
OnPropertyChanged("ActiveViewModel");
}
}
}
}
MainWindowController (shortened)
namespace Client.Controllers
{
public class MainWindowController
{
private readonly MainWindow mView;
private readonly MainWindowViewModel mViewModel;
public MainWindowController(MainWindowViewModel mViewModel, MainWindow mView)
{
this.mViewModel = mViewModel;
this.mView = mView;
this.mView.DataContext = mViewModel;
this.mViewModel.OpenUserControlCommand = new RelayCommand(ExecuteOpenUserControlCommand);
}
private void OpenUserControlCommand(object obj)
{
var userControlController = Container.Resolve<UserControlController>(); // Get Controller instance with dependency injection
mViewModel.ActiveViewModel = userControlController.Initialize();
}
}
}
UserControlSub.xaml (shortened)
<UserControl x:Class="Client.Views.UserControlSub"
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:Client.Views"
xmlns:viewModels="clr-namespace:Client.ViewModels"
mc:Ignorable="d">
<Grid>
<ListBox ItemsSource="{Binding Models}" SelectedItem="{Binding SelectedModel}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Attr}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel>
<Button Command="{Binding Add}">Kategorie hinzufügen</Button>
</StackPanel>
</Grid>
</UserControl>
UserControlViewModel (shortened)
namespace Client.ViewModels
{
public class UserControlViewModel : ViewModelBase
{
private Data _selectedModel;
public ObservableCollection<Data> Models { get; set; } = new ObservableCollection<Data>();
public Data SelectedModel
{
get => _selectedModel;
set
{
if (value == _selectedModel) return;
_selectedModel= value;
OnPropertyChanged("SelectedModel");
}
}
public ICommand Add { get; set; }
}
}
UserControlController (shortened)
namespace Client.Controllers
{
public class UserControlController
{
private readonly UserControlSub mView;
private readonly UserControlViewModel mViewModel;
public UserControlController(UserControlViewModel mViewModel, UserControlSub mView)
{
this.mViewModel = mViewModel;
this.mView = mView;
this.mView.DataContext = mViewModel;
this.mViewModel.Add = new RelayCommand(ExecuteAddCommand);
}
private void ExecuteAddCommand(object obj)
{
Console.WriteLine("This code gets never called!");
}
public override ViewModelBase Initialize()
{
foreach (var mod in server.GetAll())
{
mViewModel.Models.Add(mod);
}
return mViewModel;
}
}
}
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 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.
XAML, C# novice and am struggling to databind a variable defined in my code behind to a textblock defined in XAML. But I get not result.
Here is my XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
Loaded="Window_Loaded_1">
<Grid>
<TextBlock Name="totalRecording">
<Run Text="44 /"/>
<Run Text="{Binding Source=listlength, Path=totalRecording}"/>
</TextBlock>
</Grid>
Here is my code behind
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded_1(object sender, RoutedEventArgs e)
{
var listlength = 100;
}
}
}
For now I have just set the variable to a static number for the purposes of illustrating my problem but this variable will be obtained from a list Count value.
For binding you need to use Property only .you cannot use varibale for binding.
To create property I have created a class here . It is not necessary to create a new class to have property.
public class TextboxText
{
public string textdata { get; set; }
}
And set datacontext to textblock so that I can use this property for binding
InitializeComponent();
totalRecording.DataContext = new TextboxText() { textdata = "100" };
in xaml
<Grid Height="300" Width="400" Background="Red">
<TextBlock Name="totalRecording">
<Run Text="44 /"/>
<Run Text="{Binding textdata}"/>
</TextBlock>
</Grid
If you want to update the Binding, you should use a DependencyProperty.
First you have to create the property and a public string like this:
public static readonly DependencyProperty ListLengthProperty =
DependencyProperty.Register("ListLength", typeof(string), typeof(Window), new PropertyMetadata(null));
public string ListLength
{
get { return (string)GetValue(ListLengthProperty); }
set { SetValue(ListLengthProperty, value); }
}
Here is the XAML file, you need to set a name for the window:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="CurrentWindow"
Title="MainWindow" Height="350" Width="525"
Loaded="Window_Loaded_1">
<Grid>
<TextBlock Name="totalRecording">
<Run Text="44 /"/>
<Run Text="{Binding ListLength, ElementName=CurrentWindow}"/>
</TextBlock>
</Grid>
Now you can always update the Binding by setting the ListLength like this:
ListLength = "100";
Just use TextBlock,
<Grid Name="myGrid" Height="437.274">
<TextBox Text="{Binding Path=listlength}"/>
</Grid>
Declare the variable and Implement InotifyPropertyChanged
partial class Window1 : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _listlength;
public string Listlength
{
get { return _listlength; }
set
{
if (value != _listlength)
{
_listlength = value;
OnPropertyChanged("Listlength");
}
}
}
}
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");
}
}