WPF How to bind object from window to custom user control - wpf

I need to create a custom user control and pass it from the main window an object. I need to display the object's attribute inside the user control, how can i do it? Thanks in advance.
EDIT: This is my code, what i'm doing wrong?
My custom control:
public partial class DetailsComponent : UserControl
{
public static readonly DependencyProperty ModelProperty = DependencyProperty.Register("Model", typeof(bool), typeof(DetailsComponent), new PropertyMetadata(null));
public ModelClass Model
{
get { return (ModelClass)GetValue(ModelProperty); }
set
{
SetValue(ModelProperty, value);
}
}
public DetailsComponent()
{
InitializeComponent();
DataContext = this;
}
}
usercontrol.xaml.cs:
<UserControl x:Class="WpfApp3.DetailsComponent"
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:WpfApp3"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<StackPanel>
<TextBlock Text="{Binding Name}"></TextBlock></TextBlock>-->
</StackPanel>
</Grid>
MainWindows.xaml:
<Window x:Class="WpfApp3.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:WpfApp3" xmlns:custom="clr-namespace:LoadingSpinnerControl;assembly=LoadingSpinnerControl" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<local:DetailsComponent Model="{Binding Model}"></local:DetailsComponent>
</Grid>
MainWindows.xaml.cs:
public partial class MainWindow : Window, INotifyPropertyChanged
{
ModelClass Model = null;
public MainWindow()
{
InitializeComponent();
Model = new ModelClass("hi", "hi", "hi");
DataContext = this;
}
}
ModelClass.cs:
public class ModelClass
{
public ModelClass(string name, string description, string city)
{
Name = name;
Description = description;
City = city;
}
public string Name { get; set; }
public string Description { get; set; }
public string City { get; set; }
}

Model must be defined as a public property for you to be able to bind to it:
public partial class MainWindow : Window
{
public ModelClass Model { get; }
public MainWindow()
{
InitializeComponent();
Model = new ModelClass("hi", "hi", "hi");
DataContext = this;
}
}
The DetailsModel should not set its own DataContext property because then you cannot bind to the Model property of its inherited DataContext:
public partial class DetailsComponent : UserControl
{
public static readonly DependencyProperty ModelProperty =
DependencyProperty.Register("Model", typeof(ModelClass), typeof(DetailsComponent), new PropertyMetadata(null));
public ModelClass Model
{
get { return (ModelClass)GetValue(ModelProperty); }
set { SetValue(ModelProperty, value); }
}
public DetailsComponent()
{
InitializeComponent();
}
}
You could bind to the Name property of the Model dependency property using a RelativeSource binding:
<UserControl x:Class="WpfApp3.DetailsComponent"
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:WpfApp3"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<StackPanel>
<TextBlock Text="{Binding Model.Name,
RelativeSource={RelativeSource AncestorType=UserControl}}" />
</StackPanel>
</Grid>
</UserControl

Related

Dynamic Validation In WPF Using MVVM?

How to do text box input validation in wpf MVVM structure?
You should implement the INotifyDataErrorInfo interface in your view model:
public class ViewModel : INotifyDataErrorInfo
{
private readonly Dictionary<string, string> _validationErrors = new Dictionary<string, string>();
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
//validate:
if (_text?.Length < 3)
_validationErrors[nameof(Text)] = "Too short...";
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(Text)));
}
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public bool HasErrors => _validationErrors.Count > 0;
public IEnumerable GetErrors(string propertyName) =>
_validationErrors.TryGetValue(propertyName, out string error) ? new string[1] { error } : null;
}
View:
<Window x:Class="WpfApp1.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:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="300">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</Window>
check this: https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-implement-binding-validation and consider on this: Validation.ErrorTemplate="{StaticResource validationTemplate}"

WPF Caliburn.Micro: Binding DisplayMemberPath on ChildViewModel

I use VS2013, WPF 4.5, Caliburn Micro 2.0.2. This is a sample project of mine.
My ShellViewModel has base class Conductor and has collection of model (a property) called Vehicles. I created also ChildViewModel that has base class Screen and a property called ParentVm, which contains its parent. In my real project I have surely more than one child viewmodel (thus, > 1 screens)
The model (class Vehicle) contains properties: Manufacturer, Model and Type.
How can I bind DisplayMemberPath of ListBox in ChildView which has ItemsSource="ParentVm.Vehicles", so the ListBox can show the Manufacturer of class Vehicle?
Following is my sample code. Please feel free to modify it to show me the solution. Thank you in advance.
public class Vehicle : PropertyChangedBase
{
public String Manufacturer { get; set; }
public String Model { get; set; }
public String Type { get; set; }
}
ShellViewModel
public class ShellViewModel : Conductor<Screen>, IHaveActiveItem
{
public ChildViewModel ChildVm { get; private set; }
public ObservableCollection<Vehicle> Vehicles { get; set; }
public ShellViewModel()
{
DisplayName = "Shell Window";
ChildVm = new ChildViewModel(this);
Vehicles = new ObservableCollection<Vehicle>();
SetData();
}
public void DisplayChild()
{
ActivateItem(ChildVm);
}
private void SetData()
{ // fill collection with some sample data
vh = new Vehicle();
vh.Manufacturer = "Chevrolet";
vh.Model = "Spark";
vh.Type = "LS";
Vehicles.Add(vh);
}
}
ShellView
<UserControl x:Class="CMWpfConductorParentChild.Views.ShellView"
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"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid Width="300" Height="300" HorizontalAlignment="Center"
ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="10*" />
</Grid.RowDefinitions>
<Button x:Name="DisplayChild"
Grid.Row="0" Width="120" Height="40"
HorizontalContentAlignment="Center"
Content="Display Child View" />
<ContentControl x:Name="ActiveItem" Grid.Row="1" />
</Grid>
</UserControl>
ChildViewModel
public class ChildViewModel : Screen
{
public ShellViewModel ParentVm { get; private set; }
public ChildViewModel(ShellViewModel parent)
{
ParentVm = parent;
}
}
ChildView (the binding of DisplayMemberPath below doesn't work)
<UserControl x:Class="CMWpfConductorParentChild.Views.ChildView"
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"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid Width="300" Height="300">
<ListBox DisplayMemberPath="Manufacturer" ItemsSource="ParentVm.Vehicles" />
</Grid>
</UserControl>
You need to add the 'Binding' keyword to the 'ItemsSource' property:
<ListBox DisplayMemberPath="Manufacturer" ItemsSource="{Binding ParentVm.Vehicles}" />
I've modified your example to show how you can bind properties to the child view based on convention.
ShellViewModel
public class ShellViewModel : Screen, IShell
{
private readonly ChildViewModel _ChildView;
private readonly IEventAggregator _Aggregator;
public IList<Vehicle> vehicles = new List<Vehicle>();
public ShellViewModel(IEventAggregator aggregator, ChildViewModel childView)
{
if (aggregator == null)
throw new ArgumentNullException("aggregator");
_Aggregator = aggregator;
if (childView == null)
throw new ArgumentNullException("childView");
_ChildView = childView;
DisplayName = "Shell Window";
}
public ChildViewModel ChildView
{
get { return _ChildView; }
}
public void DisplayChild()
{
var vh = new Vehicle() { Manufacturer = "Chevrolet", Model = "Spark", Type = "LS" };
vehicles.Add(vh);
_Aggregator.PublishOnUIThreadAsync(new ShowChildViewEvent(vehicles));
}
}
ShellView
<UserControl x:Class="CaliburnDemo.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org">
<Grid Width="300" Height="300" HorizontalAlignment="Center"
ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="10*" />
</Grid.RowDefinitions>
<Button x:Name="DisplayChild"
Grid.Row="0" Width="120" Height="40"
HorizontalContentAlignment="Center"
Content="Display Child View" />
<ContentControl cal:View.Model="{Binding ChildView}" Grid.Row="1" />
</Grid>
ChildViewModel
public class ChildViewModel : Screen, IHandle<ShowChildViewEvent>
{
private BindableCollection<Vehicle> _Vehicles;
private readonly IEventAggregator _Aggregator;
public ChildViewModel(IEventAggregator aggregator)
{
if (aggregator == null)
throw new ArgumentNullException("aggregator");
_Aggregator = aggregator;
_Aggregator.Subscribe(this);
}
public BindableCollection<Vehicle> Vehicles
{
get { return _Vehicles; }
set
{
_Vehicles = value;
NotifyOfPropertyChange(() => Vehicles);
}
}
public void Handle(ShowChildViewEvent message)
{
Vehicles = new BindableCollection<Vehicle>(message.Vehicles);
}
}
ChildView
<UserControl x:Class="CaliburnDemo.ChildView"
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="300" d:DesignWidth="300">
<Grid>
<ListBox x:Name="Vehicles" DisplayMemberPath="Manufacturer" />
</Grid>
ShowChildViewEvent
public class ShowChildViewEvent
{
public ShowChildViewEvent(IList<Vehicle> vehicles)
{
Vehicles = vehicles;
}
public IList<Vehicle> Vehicles { get; private set; }
}
IShell is an empty interface used for resolving the root view from the container. You can find more on conventions in Caliburn here:
Caliburn Conventions

WPF XAML - Property update in nested controls

I have a control that exposes a string property named HeaderText in this way:
public partial class HeaderControl : UserControl
{
public static DependencyProperty HeaderTextProperty;
[Category("Header Properties")]
public string HeaderText
{
get { return (string)GetValue(HeaderTextProperty); }
set { SetValue(HeaderTextProperty, value); }
}
static HeaderControl()
{
HeaderTextProperty = DependencyProperty.Register("HeaderText", typeof(string), typeof(HeaderControl), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
}
public HeaderControl()
{
this.DataContext = this;
InitializeComponent();
}
}
HeaderControl's Xaml:
<UserControl x:Class="Col.HMI.Controls.HeaderControl"
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="300" d:DesignWidth="300">
<Border Background="{Binding Path=HeaderBackground}" >
<TextBlock Text="{Binding Path=HeaderText}" Foreground="White" TextAlignment="Left" VerticalAlignment="Center" HorizontalAlignment="Stretch" FontFamily="Segoe UI Light" FontSize="36" Margin="5"/>
</Border>
and I want to use this HeaderControl in another UserControl, in this way:
OtherControl's Xaml:
<controls:HeaderControl Grid.Row="0" HeaderText="DEMO" />
And this works without problems. But if I bind the HeaderText property to a string property in the OtherControl ViewModel, in this way:
<controls:HeaderControl Grid.Row="0" HeaderText="{Binding Path=SummaryTitle}" />
the bind doesn't work.
This is the SummaryTitle property in the OtherControl ViewModel:
public string SummaryTitle
{
get
{
return _summaryTitle;
}
set
{
_summaryTitle = value; OnPropertyChanged("SummaryTitle");
}
}
PS: I have other controls binded to the OtherControl View Model and they work well.
You are setting DataContext of HeaderControl to itself in the constructor by doing this:
this.DataContext = this;
That means, when you apply some binding to any of the properties in HeaderControl, the Binding engine tries to find the bound property (in your case SummaryTitle) in this control, which it wont find and will fail.
So, to fix your problem, do not set the DataContext of HeaderControl to itself in the Constructor and the Binding engine will try find the properties in the correct DataContext.
Update your HeaderControl constructor to the following, and the bindings should start to work:
public HeaderControl()
{
InitializeComponent();
}
UPDATE
What you are trying to do here is, You want to have DependencyProperty named HeaderText in your UserControl, so that it's value can be set via DataBinding, and then update a value of TextBlock in your UserControl with the value of that DependencyProperty.
You can achieve this by two ways:
1) By updating TextBlock Binding to use ElementNme and Path syntax, XAML would look like this:
<UserControl x:Class="WpfApplication1.HeaderControl"
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="300" d:DesignWidth="300"
x:Name="_This">
<Grid>
<TextBlock Text="{Binding ElementName=_This, Path=HeaderText}" FontSize="24" />
</Grid>
</UserControl>
With this approach, whenever the property HeaderText is changed either via Binding or explicitly setting the value.
2) By listening to property value changed event for HeaderText property and then updating the TextBlock accordingly.
For this approach your HeaderControl.xaml would look like:
<UserControl x:Class="WpfApplication1.HeaderControl"
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="300" d:DesignWidth="300" >
<Grid>
<TextBlock x:Name="TextBlockInUserControl"/>
</Grid>
</UserControl>
and the HeaderControl.xaml.cs
public partial class HeaderControl : UserControl
{
public static readonly DependencyProperty HeaderTextProperty;
[Category("Header Properties")]
public string HeaderText
{
get { return (string)GetValue(HeaderTextProperty); }
set { SetValue(HeaderTextProperty, value); }
}
static HeaderControl()
{
HeaderTextProperty = DependencyProperty.Register("HeaderText", typeof (string), typeof (HeaderControl),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnHeaderTextPropertyChanged));
}
private static void OnHeaderTextPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var headerControl = (HeaderControl) dependencyObject;
headerControl.UpdateTextBlock((string) dependencyPropertyChangedEventArgs.NewValue);
}
void UpdateTextBlock(string text)
{
TextBlockInUserControl.Text = text;
}
public HeaderControl()
{
InitializeComponent();
}
}

binding to a usercontrol not working

i have encountered a databinding probleme,
so i create a usercontrole
UserControl1.xaml.cs
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public static DependencyProperty TestThisProperty = DependencyProperty.Register
(
"TestThis",
typeof(string),
typeof(UserControl1),
new PropertyMetadata("Some Data",new PropertyChangedCallback(textChangedCallBack))
);
public string TestThis
{
get { return (string)GetValue(TestThisProperty); }
set { SetValue(TestThisProperty, value); }
}
static void textChangedCallBack(DependencyObject property, DependencyPropertyChangedEventArgs args)
{
UserControl1 _us = (UserControl1)property;
_us.MyUserControl.TestThis = (string)args.NewValue;
}
}
UserControl1.xaml
<UserControl x:Class="WpfApplication1.UserControl1"
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" x:Name="MyUserControl"
d:DesignHeight="300" d:DesignWidth="300">
</UserControl>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
UserControl1 _uc = new UserControl1();
public MainWindow()
{
InitializeComponent();
DispatcherTimer _dt = new DispatcherTimer();
_dt.Interval = new TimeSpan(0, 0, 1);
_dt.Start();
_dt.Tick += new EventHandler(_dt_Tick);
}
private void _dt_Tick(object s,EventArgs e)
{
_uc.TestThis = DateTime.Now.ToString("hh:mm:ss");
}
and finaly the MainWindow.xaml
<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>
<TextBox Text="{Binding Path=TestThis,ElementName=MyUserControl, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
but the problem here, whene i debug my code i get this warning
Cannot find source for binding with reference 'ElementName=MyUserControl'. and the ui does not update of course,any idea please?
ElementName only targets names in the same namescope, e.g.
<TextBox Name="tb"/>
<Button Content="{Binding Text, ElementName=tb}"/>
If you do not define the UserControl in the MainWindow XAML and give it a name there or register a name in code behind (and target that) you won't get this ElementName binding to work.
An alternative would be exposing the usercontrol as a property on the MainWindow, e.g.
public UserControl1 UC { get { return _uc; } }
Then you can bind like this:
{Binding UC.TestThis, RelativeSource={RelativeSource AncestorType=Window}}

How to databind public property in xaml

All I am trying to do is bind a public property to a textBlock. What am I doing wrong here?
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public string test { get; set; }
public MainWindow()
{
test = "this is a test";
InitializeComponent();
}
}
}
<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">
<Window.Resources>
<ObjectDataProvider x:Key="test"></ObjectDataProvider>
</Window.Resources>
<Grid>
<TextBlock Height="23" HorizontalAlignment="Left" Margin="108,58,0,0" Name="textBlock1" VerticalAlignment="Top" Text="{Binding Source={StaticResource test}}" />
</Grid>
You can simply add a datacontext and access your property
public partial class MainWindow : Window,INotifyPropertyChanged
{
private string _test;
public string test
{
get
{
return _test;
}
set
{
_test = value;
OnPropertyChanged("test");
}
}
public MainWindow()
{
test = "this is a test";
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(String name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
<TextBlock Height="23" HorizontalAlignment="Left" Margin="108,58,0,0" Name="textBlock1" VerticalAlignment="Top" Text="{Binding test}"/>
Also check this post for details of when to use an ObjectDataProvider
http://bea.stollnitz.com/blog/?p=22
At first you need you class to implement INotifyPropertyChanged or a property to be DependencyProperty for changing property value on textbox text change,
namespace WpfApplication1
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
private string _test
public string test
{
get
{
return _test;
}
set
{
_test = value;
OnPropertyChanged("test");
}
}
public MainWindow()
{
test = "this is a test";
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
}
Than you can bind to that property by giving name to that window, and using ElementName property 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"
Title="MainWindow" Height="350" Width="525" Name="myWindow">
<Window.Resources>
<ObjectDataProvider x:Key="test"></ObjectDataProvider>
</Window.Resources>
<Grid>
<TextBlock Height="23" HorizontalAlignment="Left" Margin="108,58,0,0" Name="textBlock1" VerticalAlignment="Top" Text="{Binding ElementName=myWindow, Path=test}" />
</Grid>
You actually don't need to implement INotifyPropertyChanged. However, this will be a one time data binding.
For example in XAML:
<TextBlock Name="SomeTextBlock" Text="{Binding Path=SomeProp}" />
In Code:
public string SomeProp { get; set; }
public MainWindow()
{
InitializeComponent();
SomeProp = "Test Test Test";
SomeTextBlock.DataContext = this;
}

Resources