Binding to properties in both the ViewModel and CodeBehind - c#

I have what I'm sure is a ridiculously ignorant question, but I'm asking it anyways because I've searched and searched and either don't understand the solutions I'm seeing or not finding exactly the answer I seek.
I have an MVVM application. My XAML is setup with the DataContext set to the VM where the data items on the screen are populated from the VM's properties. My CodeBehind doesn't fiddle with the data, only things relating to the screen.
What I want to do now is bind certain UI elements to properties in the foo.xaml.cs (CodeBehind) file. For example, I want to specify FontSize's bound to properties in the CB so that in the WindowInitialized handler in the CB, it can detect screen sizes and change one variable to which all the screen items' FontSize= are bound.
I can solve this the wrong way by creating a public property in my VM and then "inject" the value from the CB into the VM. I know that will work, but it's a roundabout way to get the behavior I want, it's not at all straightforward, and I feel confident it's the wrong way to proceed.
I searched around and have tried things like:
FontSize="{Binding RelativeSource={RelativeSource Self},Path="MyFontSize"
(where "MyFontSize" is a public int property) and a variety of other examples I found, but none have worked.
So specifically, if my CodeBehind class is called NameChangeSetupMainWindow and that's where the "MyFontSize" property lives,
public partial class NameChangeSetupMainWindow : Window
{
private int m_fontSize = 14;
public int MyFontSize
{
get { return m_fontSize; }
set
{
if (m_fontSize != value))
{
m_fontSize = (value > 0) ? value : 10;
}
}
}
...
... rest of the class...
...
}
and the VM is called NameChangeSetupViewModel and that's where the "real" data lives and the DataContext points ala:
<Window.DataContext>
<local:NameChangeSetupViewModel/>
</Window.DataContext>
what is the syntax in XAML to bind just those UI items (tooltips related to the UI, font sizes, etc) to variables in the CodeBehind instead of housing them in the VM?
Thanks in advance for any guidance you can supply.

You can use RelativeSource AncestorType to bind to properties of the view itself:
<TextBlock FontSize="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=MyFontSize}" />
Using ElementName should work as well:
<Window x:Name="window">
<TextBlock FontSize="{Binding ElementName=window,Path=MyFontSize}" />
</Window>
Edit
Here is an example that I've confirmed working:
XAML
<Window x:Class="WpfAbc.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"
ToolTip="{Binding RelativeSource={RelativeSource Self},Path=MyToolTip}"
>
<Grid>
<TextBlock Text="hello world" FontSize="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=MyFontSize}" />
</Grid>
</Window>
Code Behind
public partial class MainWindow : Window
{
private int m_fontSize = 20;
public int MyFontSize
{
get { return m_fontSize; }
set
{
if (m_fontSize != value)
{
m_fontSize = (value > 0) ? value : 10;
}
}
}
public string MyToolTip
{
get { return "hello world"; }
}
public MainWindow()
{
InitializeComponent();
}
}
Articles on this topic:
The RelativeSource markup extension
XAML binding declarations
Related background:
"Namescopes" in XAML (when binding to a source using "ElementName", the source element must be in the same namescope)
Visual tree vs logical tree in XAML (elements not in the visual tree, like Popup and ContextMenu, do not inherit DataContext. Binding from these elements requires a workaround like the "data context spy" technique.)

Related

Adding to XAML WPF from ViewModel (MVVM)

first attempt at MVVM and WPF (steep learning curve).
In my ViewModel I want to run the following code to add a "layoutDocument" which is an AvalonDock layout into my Mainform UI.
ViewModel class:
LayoutDocument layoutDocument = new LayoutDocument { Title = "Plan Layout" };
Window mainWindow = Application.Current.Windows.OfType<Window>().Where(x => x.Name == "MainWindow").FirstOrDefault();
if (mainWindow != null)
{
mainWindow.mainPanel.Children.Add(layoutDocument);
}
The above code gives me the following error:
"'Window' does not contain definition for 'mainPanel' and no extension method for 'mainPanel'".
Note in my XAML below that "LayoutDocumentPane" does contain a name "mainPanel".
I have tried adding the above code directly into my MainForm View Class (excluding the Application.Current.Windows.OfType and If statement bit) and just including the:
mainPanel.Children.Add(layoutDocument);
And it works fine (a new layout is created in my MainForm when I click the button).
However, as I want to stickto MVVM this is not a suitable solution.
How can I add "layoutDocument" to MainWindow from ViewModel? Thanks in advance.
An extract of my XAML looks like this:
<Window x:Class="LiveExplorer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:LiveExplorer"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:xcad="http://schemas.xceed.com/wpf/xaml/avalondock"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:vm="clr-namespace:WpfApp1.ViewModel">
<Grid> etc etc etc here---
<xcad:LayoutDocumentPaneGroup>
<xcad:LayoutDocumentPane x:Name="mainPanel">
<xcad:LayoutDocument ContentId="document1" Title="Document 1" >
<Button Content="Document 1 Content" HorizontalAlignment="Center" VerticalAlignment="Center"
Command="{Binding NewPlanCommand, Source={StaticResource viewModel}}"
/>
</xcad:LayoutDocument>
<xcad:LayoutDocument ContentId="document2" Title="Document 2">
<TextBox Text="Document 2 Content" AcceptsReturn="True"/>
</xcad:LayoutDocument>
</xcad:LayoutDocumentPane>
</xcad:LayoutDocumentPaneGroup >
EDIT:
Whilst the accepted answer does not answer the question in terms of MMVM, it does correct the coding error.
What you've tried to implement does not follow the MVVM pattern. You need to take care of 3 things to get started:
ViewModels
Initialize the ViewModel binded to the window
Binding ViewModel to the UI in XAML
ViewModels:
Create a viewmodel that will be binded to your MainWindow and create an observable collection inside that MainWindowViewModel that contains a list of object that will contain data that can be used in the UI:
public ObservableCollection<LayoutDocumentViewModel> LayoutDocument {get;set;}
Make sure that both the MainWindowViewModel and the LayoutDocumentViewModel inherits from INotifyPropertyChanged(Implement Property Change Notification) or if you use MVVMLight (or similar) from ViewModelBase.
The LayoutDocumentViewModel is just a ViewModel that will be used to store information about your layout document and that can be binded to the UI.
public LayoutDocumentViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName]
string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("Name");
}
}
}
I would strongly recommend that you use MVVMLight (or similar) or put the INotifyPropertyChange code into a base class i.e. ViewModelBase for example.
For simplicity sake in this example, I'm initializing the observable collection and creating a couple of document layouts objects directly in the MainWindowViewModel but you'll need to research this further and find out where it is appropriate for you to initialize and/or create these.
public MainPageViewModel()
{
DocumentLayouts = new ObservableCollection();
DocumentLayouts.Add(new DocumentLayout {Name="Layout1"});
DocumentLayouts.Add(new DocumentLayout {Name="Layout2"});
}
The above takes care of creating your MainWindowViewModel and layout documents.
Initializing MainViewModel (and binded to the MainWindow.xaml). Note this is a quick and dirty way to get you started and you should really look into IoC containers.
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
Finally, bind your ViewModel & UI
XAML:
<Grid>
<ItemsControl ItemsSource="{Binding LayoutDocuments}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Note: Just replace the Label by your LayoutDocument control and bind it to the relevant element properties you have declared in LayoutDocumentViewModel.
Hope this helps get you started.
This is not related to MVVM but to be able access the mainPanel you need to cast the returned Window to a MainWindow:
MainWindow mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
if (mainWindow != null)
{
mainWindow.mainPanel.Children.Add(layoutDocument);
}
A view model shouldn't access any window directly though. This breaks the MVVM pattern.

Trouble with static fields when binding

I have a static bool property in a Model class, which I expose to two different ViewModel classes. One of these ViewModel's has a bool property linked to said static property and is bound to the Visibility of a button via a converter. This can then be set within that ViewModel to true or false and the button's visibility changes accordingly. (The instance of this ViewModel is set in the XAML of the View, via DataContext, in which the button resides)
I want to be able to change this buttons visibility from within a different View, and I thought that by having a property in my separate View's ViewModel that is also linked to my static bool in my original model, I could do this, but it isn't doing anything.
Here's my code:
MainModel
public class MainModel
{
static bool _ButtonIsVisible = true;
public static bool ButtonIsVisible
{
get { return _ButtonIsVisible; }
set { _ButtonIsVisible = value; }
}
}
MainViewModel
class MainViewModel: ObserveableObject
{
public bool ButtonIsVisible
{
get { return MainModel.ButtonIsVisible; }
set
{
MainModel.ButtonIsVisible = value;
RaisePropertyChanged("ButtonIsVisible");
}
}
}
MainView
<Window x:Class="MVVM.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:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:MVVM"
mc:Ignorable="d"
Title="MainWindow" Width="1920" Height="1080" WindowState="Maximized" WindowStyle="None">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter"/>
</Window.Resources>
<Button Visibility="{Binding ButtonIsVisible, Converter={StaticResource BoolToVisConverter}}" />
</Window>
ButtonIsVisible from MainViewModel is changed within a command and this works as is expected. This is where my troubles occur.
AnotherViewModel
class AnotherViewModel: ObserveableObject
{
public bool ButtonIsVisible
{
get { return MainModel.ButtonIsVisible; }
set
{
MainModel.ButtonIsVisible = value;
RaisePropertyChanged("ButtonIsVisible");
}
}
}
An instance of AnotherViewModel is created via the DataContext of it's corresponding view, and a command is bound to a button within this view in which I change the ButtonIsVisible property from AnotherViewModel, at which point I would expect my button from my MainView to change, seeing that both ViewModels get and set the values of the properties in question from a static property in my MainModel, but this isn't working.
Can anyone tell me what I'm doing wrong?
As per your comments, you've got something like this:
<ContentControl Content="{Binding ViewModel}" />
And when you want to show it, you are doing this:
ViewModel = new AnotherViewModel();
So what you need in your AnotherViewModel is:
MainViewModel MVM;
public AnotherViewModel(MainViewModel _mvm)
{
this.MVM=_mvm;
}
You must then change your AnotherVieModel Instantation to:
ViewModel = new AnotherViewModel(this);
And when you want to change the button's visibility,you'll just need to do this:
this.MVM.ButtonIsVisible=true;
As I told you,this is just a way to do it, but I think is pretty straightforward and understable. If you've got doubts, feel free to ask.

MVVM Basic ContentControl Binding

I'm trying to figure out the correct way to bind a ViewModel to a ContentControl (I've looked all over the net but can't find an example that I can get to work correctly).
My Model:
public class Model
{
private string _Variable = "TEST";
public string Variable
{
get { return _Variable; }
set { _Variable = value; }
}
}
My ViewModel
public class ViewModel :ViewModelBase
{
private Model _Model = new Model();
public string Variable
{
get { return _Model.Variable; }
set
{
if (_Model.Variable != value)
{
_Model.Variable = value;
RaisePropertyChanged("Variable");
}
}
}
My View/Window
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type System:String}">
<TextBox/>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ContentControl Content="{Binding Path=Variable}" />
</StackPanel>
So in essence, I have (or at least I believe I have) set the content of the ContentControl to the ViewModel property 'Variable', it is of type string so the only DataTemplate should be implemented and a Textbox displayed.
And that happens... A Textbox is displayed! However the Textbox is empty, and any changes made do not impact Variable.
This means I have made an error in the Batabinding, but I don't understand where. I have a feeling that just because my DataTemplate is displaying a Textbox, nothing is actually binding the string to it, but that is the bit I'm lost over.
Thanks for any help/advice.
You haven't specified the TextBox's Text binding, which is completely separate to the DataContext. Since you want the TextBox to bind to its own DataContext just do this:
<TextBox Text="{Binding Path=.}"/>
use textbox as below:
<TextBox Text="{Binding}" />

Bind SelectedValue of UserControl to ViewModel

In my solution; I have two projects: One is a WPF UserControl Library, and the other is a WPF Application.
The usercontrol is pretty straightforward; it's a label and a combo box that will show the installed printers.
In the WPF application; I want to use this usercontrol. The selected value will be stored in user settings.
The problem I'm having is that I can't seem to get the proper binding to work. What I need to happen is to be able to set the SelectedValue of the UserControl when the MainWindow loads; as well as access the SelectedValue of the UserControl when I go to save my settings.
My code is below, could someone point me in the right direction?
PrintQueue user control:
<UserControl x:Class="WpfControls.PrintQueue"
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:wpfControls="clr-namespace:WpfControls"
mc:Ignorable="d">
<UserControl.DataContext>
<wpfControls:PrintQueueViewModel/>
</UserControl.DataContext>
<Grid>
<StackPanel Orientation="Horizontal">
<Label Content="Selected Printer:"></Label>
<ComboBox ItemsSource="{Binding Path=PrintQueues, Mode=OneWay}" DisplayMemberPath="Name" SelectedValuePath="Name" Width="200" SelectedValue="{Binding Path=SelectedPrinterName, Mode=TwoWay}"></ComboBox>
</StackPanel>
</Grid>
</UserControl>
Print Queue Codebehind:
public partial class PrintQueue : UserControl
{
public static readonly DependencyProperty CurrentPrinterNameProperty =
DependencyProperty.Register("CurrentPrinterName", typeof (string), typeof (PrintQueue), new PropertyMetadata(default(string)));
public string CurrentPrinterName
{
get { return (DataContext as PrintQueueViewModel).SelectedPrinterName; }
set { (DataContext as PrintQueueViewModel).SelectedPrinterName = value; }
}
public PrintQueue()
{
InitializeComponent();
DataContext = new PrintQueueViewModel();
}
}
PrintQueue View Model:
public class PrintQueueViewModel : ViewModelBase
{
private ObservableCollection<System.Printing.PrintQueue> printQueues;
public ObservableCollection<System.Printing.PrintQueue> PrintQueues
{
get { return printQueues; }
set
{
printQueues = value;
NotifyPropertyChanged(() => PrintQueues);
}
}
private string selectedPrinterName;
public string SelectedPrinterName
{
get { return selectedPrinterName; }
set
{
selectedPrinterName = value;
NotifyPropertyChanged(() => SelectedPrinterName);
}
}
public PrintQueueViewModel()
{
PrintQueues = GetPrintQueues();
}
private static ObservableCollection<System.Printing.PrintQueue> GetPrintQueues()
{
var ps = new PrintServer();
return new ObservableCollection<System.Printing.PrintQueue>(ps.GetPrintQueues(new[]
{
EnumeratedPrintQueueTypes.Local,
EnumeratedPrintQueueTypes.Connections
}));
}
}
Main Window:
<Window x:Class="WPFApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfControls="clr-namespace:WpfControls;assembly=WpfControls" xmlns:wpfApp="clr-namespace:WPFApp"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<wpfApp:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<wpfControls:PrintQueue CurrentPrinterName="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.PrinterName, Mode=TwoWay}"></wpfControls:PrintQueue>
</StackPanel>
</Grid>
</Window>
Main Window View Model:
public class MainWindowViewModel : ViewModelBase
{
private string printerName;
public string PrinterName
{
get { return printerName; }
set
{
printerName = value;
NotifyPropertyChanged(() => PrinterName);
}
}
public MainWindowViewModel()
{
PrinterName = "Lexmark T656 PS3";
}
}
Controls in a library need to expose DependencyProperties that you can bind to in your view. Just like WPF's TextBox exposes a Text property.
Your PrintQueue control doesn't expose anything, and instead keeps all its state in a viewmodel that nothing outside can access. Your MainWindowViewModel has no way of getting at the stuff inside PrintQueueViewModel.
You need to expose SelectedPrinterName as a DependencyProperty in the code behind of your PrintQueue xaml. Then in MainWindow.xaml you can bind it to MainWindowViewModel.PrinterName.
If you want to user ViewModels all the way through instead, then MainWindowViewModel should be creating PrintQueueViewModel itself so it can access the properties within.
As per your update / comment:
Unfortunately DependencyProperties don't work like that. The getters/setters aren't even used most of the time, and they should ONLY update the property itself. You're sort of halfway between two worlds at the moment.
If I were in your position, and assuming you can change the library so PrintQueue.xaml doesn't have a hardcoded VM instance in the view, I would just create the PrintQueueViewModel yourself. That's how MVVM is supposed to work:
ViewModel:
public class MainWindowViewModel : ViewModelBase
{
public PrintQueueViewModel PrintQueue { get; private set; }
public MainWindowViewModel()
{
PrintQueue = new PrintQueueViewModel();
PrintQueue.SelectedPrinterName = "Lexmark T656 PS3";
}
}
View:
<Window x:Class="WPFApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfControls="clr-namespace:WpfControls;assembly=WpfControls" xmlns:wpfApp="clr-namespace:WPFApp"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<wpfApp:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<wpfControls:PrintQueue DataContext="{Binding PrintQueue}"/>
</StackPanel>
</Grid>
</Window>
Again though, control libraries generally don't have view models, and expose their state via dependency properties since they're designed to be used in XAML.
Component libraries may expose view models, but in that case they wouldn't hard code the view model in the view.
Did you write the library? If not, how did the author expect people to use it?
I think with this small changes everything should work
<ComboBox ItemsSource="{Binding Path=PrintQueues, Mode=OneWay}" DisplayMemberPath="Name" Width="200" SelectedItem="{Binding Path=SelectedPrinter, Mode=TwoWay}"></ComboBox>
private System.Printing.PrintQueue selectedPrinter;
public System.Printing.PrintQueue SelectedPrinter
{
get { return selectedPrinter; }
set
{
selectedPrinter = value;
NotifyPropertyChanged(() => SelectedPrinter);
}
}
Now from the main window you can modify SelectedPrinter on the viewmodel and the change should be reflected on the view
(PrintQueue.DataContext as PrintQueueViewModel).SelectedPrinter = ...
I tried your code and your bindings of the PrintQueueView to the corresponding view model work fine. Your problem is that the MainWindowViewModel does not know about the PrintQueueViewModel and thus cannot retrieve the value of the selected printer when the main window closes (I guess that is the scenario you want to implement).
The quickest solution to your problem would be to do the following steps:
In MainWindow.xaml, give PrintQueue a Name so you can access it in the code behind
In MainWindow.xaml.cs, override the OnClosing method. In it you can retrieve the view model as follows: var viewModel = (PrintQueueViewModel)PrintQueue.DataContext;. After that you can retrieve the selected value and save it or whatever.
In the MainWindow constructor after InitializeComponent, you can retrieve your saved value from a file and set it on the PrintQueueViewModel by retrieving it the same way as in the previous step.
Whole code in MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Retrieve your selected printer here; in this case, I just set it directly
var selectedPrinter = "Lexmark T656 PS3";
var viewModel = (PrintQueueViewModel)PrintQueue.DataContext;
viewModel.SelectedPrinterName = selectedPrinter;
}
protected override void OnClosing(CancelEventArgs e)
{
var viewModel = (PrintQueueViewModel)PrintQueue.DataContext;
var selectedPrinterName = viewModel.SelectedPrinterName;
// Save the name of the selected printer here
base.OnClosing(e);
}
}
Please remember that the major point of view models is the ability to unit-test GUI logic and to disconnect GUI appearance and logic. Your view models should not be able to retrieve all the possible printers of your system but should obtain these values by e.g. Dependency Injection. I would advise you to read about SOLID programming.

WPF with MVVM and DataAnnotations, Validation Errors in a UserControl

I have a UserControl that will be reused throughout an application we are developing.
We are using a framework based on MVVMLight.
For the sake of simplicity lets say the user control contains only one textbox and exposes one dependency property named "Quantity". The textbox on the user control is databound to the dependency property "Quantity".
When the user control is used on a view, the "Quantity" dependency property of the usercontrol is databound to a property in a ViewModel. (This ViewModel is the datacontext of our view by way of the MVVMLight ViewModelLocator).
This all works great! The bindings work, properties are set like I would expect. All is well until it comes to validation.
We are using DataAnnotations to decorate our ViewModel properties. The ViewModel contains a custom implementation of INotifyDataErrorInfo. We have implemented custom styles for most input controls to show a red border around the control, and a message next to the control displaying the validation error message. All of this works great in a normal case (eg. Textbox on a View bound to a property in a view model).
When I attempt the same approach using this user control, what I end up with is a red border around the entire user control and no error indication on the actual textbox. It appears that the fact that there is an error is being reflected in the UI, but it's just not making it to the control I want it to.
I've searched on stackoverflow for this problem, of those questions with solutions, none seem to work for my situation.
My first guess is that because the actual textbox is bound directly to the dependency property itself and not the property on my view model, it is not being notified properly of the errors generated. Is there some way to propogate those errors generated in the viewmodel through the usercontrol and then to the textbox?
Any help or suggestions you can give would be great, thanks.
Here is the UserControl xaml.
<UserControl x:Class="SampleProject.UserControls.SampleControl"
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="sampleControl"
d:DesignHeight="300" d:DesignWidth="300">
<Grid x:Name="LayoutRoot" DataContext="{Binding ElementName=sampleControl}">
<TextBox Text="{Binding Path=Quantity, ValidatesOnDataErrors=True}" Width="100" Height="30" />
</Grid>
</UserControl>
The UserControl code behind.
public partial class SampleControl : UserControl
{
public SampleControl()
{
InitializeComponent();
}
public static readonly DependencyProperty QuantityProperty =
DependencyProperty.Register("Quantity", typeof(int?), typeof(SampleControl),
new FrameworkPropertyMetadata{DefaultValue=null, BindsTwoWayByDefault = true});
public int? Quantity
{
get { return (int?)GetValue(QuantityProperty); }
set { SetValue(QuantityProperty, value); }
}
}
Used on a view.
<userControls:SampleControl Grid.Row="1" Quantity="{Binding Path=Quantity, ValidatesOnDataErrors=True}" Height="60" Width="300"/>
The ViewModel property.
[Required(ErrorMessage = "Is Required")]
[Range(5, 10, ErrorMessage = "Must be greater than 5")]
public int? Quantity
{
get { return _quantity; }
set { Set(() => Quantity, ref _quantity, value); }
}
private int? _quantity;
(*Note, The Set method in the setter is just a helper method in the base viewmodel that sets the backing property and raises the PropertyChanged event for it.)
Try removing the DataContext from the UserControl. Instead of setting that, Bind directly from the TextBox to the actual property using a RelativeSource Binding:
<TextBox Text="{Binding Quantity, RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type YourControlNamespace:SampleControl,
ValidatesOnDataErrors=True}}}" Width="100" Height="30" />
UPDATE >>>
Failing that, as long as the view models that are bound to this property will always have a property of the same name to bind to, you can get this Binding to search through parents' DataContexts like this:
<TextBox Text="{Binding Quantity, RelativeSource={RelativeSource Mode=FindAncestor,
AncestorLevel=2, ValidatesOnDataErrors=True}}}" Width="100" Height="30" />
You will need to change the 2 to be the correct number of parent elements that the TextBox has before reaching the control with access to the correct property. For example, using a level of 2 means that the Framework will try to find a property named Quantity to Bind to in the DataContext of the TextBoxs parent's parent control. It is trickier getting this working with AncestorLevel though as I believe that 'hidden' elements like Grids are not included as parents.
You need to pick up the bindings set on the usercontrol and place them on the controls, there is no need to bind the usercontrol to it's own DataContext.
This can be done after the usercontrol is loaded.
To prevent a red border round the user control, remove the default error template:
Validation.ErrorTemplate="{x:Null}"
Sample user control XAML:
UserControl x:Class="DxUserControlValidation.MyUserControl"
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"
Validation.ErrorTemplate="{x:Null}"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Vertical">
<TextBlock Text="Value 1:" Margin="2"/>
<TextBox Name="txtBox1" Margin="2"/>
<TextBlock Text="Value 2:" Margin="2"/>
<TextBox Name="txtBox2" Margin="2"/>
</StackPanel>
public partial class MyUserControl : UserControl
{
public static readonly DependencyProperty Value1Property;
public static readonly DependencyProperty Value2Property;
static MyUserControl()
{
Value1Property = DependencyProperty.Register("Value1", typeof(string), typeof(MyUserControl), new FrameworkPropertyMetadata { DefaultValue = null, BindsTwoWayByDefault = true });
Value2Property = DependencyProperty.Register("Value2", typeof(string), typeof(MyUserControl), new FrameworkPropertyMetadata { DefaultValue = null, BindsTwoWayByDefault = true });
}
public MyUserControl()
{
InitializeComponent();
Loaded += (s, e) =>
{
Binding value1Binding = BindingOperations.GetBinding(this, Value1Property);
if (value1Binding != null) txtBox1.SetBinding(TextBox.TextProperty, value1Binding);
Binding value2Binding = BindingOperations.GetBinding(this, Value2Property);
if (value2Binding != null) txtBox2.SetBinding(TextBox.TextProperty, value2Binding);
};
}
public string Value1
{
get { return (string)GetValue(Value1Property); }
set { SetValue(Value1Property, value); }
}
public string Value2
{
get { return (string)GetValue(Value2Property); }
set { SetValue(Value2Property, value); }
}
}
If there is no binding, you van assign the value directly to the control:
if (value2Binding != null) txtBox2.SetBinding(TextBox.TextProperty, value2Binding);
else txtBox2.Text = Value2;

Categories

Resources