How do I create UserControl that binds "normally"? - c#

I will explain what I mean.
Let's say I wrapped TextBox into UserControl and expose property Id
In order to bind to this property, it has to be Dependency property. Fine, here we go(notice stupid dance with OnIdChanged calls property setter so we get INotifyPropertyChanged working):
public static readonly DependencyProperty IdProperty =
DependencyProperty.RegisterAttached("Id", typeof(string), typeof(MyTextBox), new PropertyMetadata(OnIdChanged));
public string Id
{
get
{
return (string)GetValue(IdProperty);
}
set
{
this.SetValue(IdProperty, value);
this.OnPropertyChanged("Id");
}
}
private static void OnIdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as Lookup).Id = e.NewValue as string;
}
So, this seems like all I need. I create another User control. Drop MyTextBox on it:
<Lookup:MyTextBox Id="{Binding Source={StaticResource DataContextProxy}, Path=DataSource.CurrentItem.DeviceId, Mode=TwoWay, NotifyOnValidationError=True}"/>
As you see - I had to use DataContextProxy. To be honest, it's little bit of magic for me, I did it once and tried it now when regular way wasn't binding. How should I code my user control so I can bind to it like so?
<Lookup:MyTextBox Id="{Binding Path=CurrentItem.DeviceId, Mode=TwoWay, NotifyOnValidationError=True}"/>
This is how I can bind TextBox next to my custom one and it works as expected. What is the secret?
EDIT
Below is 2 screenshots. First one shows how what I get as a source when I bind to Lookup control (custom UserControl) - points to SELF
Second one - next field in my XAML - is regular textbox, binds to same CurrentItem but it sources from my ViewModel
EDIT 2
I figured why DataContext was pointing to UserControl itself. I figured why but do not understand why..
In my UserControl (Lookup) code behind after initializiation I set this.DataContext = this so inside control it binds to internal properties. Somehow it propogated to parent ViewModel. After I changed this code to LayoutRoot.DataContext = this - issue resolved. But I don't understand why it behaves like this and I still can't get good property routing through..

I covered this issue in a blog post which I wrote some time ago. If you set the DataContext of the UserControl to itself, you can no longer place it within another UserControl or Window and expect it to inherit the DataContext of its parent. This means that you cannot just sit it in your view and specify bindings to your view model. The reasons for this is that you have blocked inheritence of your ViewModel DataContext. Any properties exposed by your UserControl will have their binding Source set to the UserControl.
The solution is to set DataContext of some element within the UserControl to the UserControl itself. Most typically you would set the immediate child of the UserControl.

Related

Binding vs x:Bind: Why isn't the view updated when using Binding?

I am developing an UWP app leveraging the MVVM paradigm. My view contains a simple TextBox that has its Text property bound to a respective ViewModel property:
<TextBox Text="{Binding Path=Radius, Mode=TwoWay}"/>
Naturally, I've assigned my ViewModel to the page's DataContext:
public sealed partial class ExamplePage : Page
{
private ExamplePageVM viewModel;
public ExamplePage()
{
this.InitializeComponent();
viewModel = new ExamplePageVM();
DataContext = viewModel;
}
}
In the ViewModel I perform some kind of input validation, i. e. if the user inserts an invalid float value into the TextBox I want to reset the TextBox to a default value (zero, for instance):
class ExamplePageVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private float radius;
public string Radius
{
get => radius.ToString();
set
{
if (radius.ToString() != value)
{
if (!float.TryParse(value, out radius)) radius = 0;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Radius)));
}
}
}
}
Changing the value in the TextBox causes the setter to be called as intended. Also, the PropertyChanged event is invoked accordingly. However, the TextBox still contains invalid data after the setter execution has finished, which means that the view isn't updated correctly.
According to the first comment on this post, the solution to this issue is using <TextBox Text="{x:Bind viewModel.Radius, Mode=TwoWay}"/> instead of the Binding approach shown above. Why is that so? What's the difference between Binding and x:Bind in this very situation?
You may want to set the UpdateTrigger yourself since TextBox normally updates the source when focus lost gets called.
You can change the behaviour UpdateSourceTrigger=PropertyChanged.
<TextBox Text="{x:Bind AnswerText, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding AnswerText, UpdateSourceTrigger=PropertyChanged}"/>
If this is not working you may want to prevent inputs different then numbers with the keydown event. Which you could outsource in a user control for reuse.
Hope this helps.
Binding to TextBox.Text is a rather special case, because Microsoft made a decision where the most common scenario is that the binding should be updated when control loses focus, as opposed to each and every input text change. This allows 2 things:
somewhat more efficient handling of larger texts
safeguarding user input in progress from being changed by application
In the absence of publicly available UWP source code it's possible that MS developers may provide you with more reliable insight, but even comparing the changes to a bound source with direct tracking of the EditBox.TextProperty via DependencyObject.RegisterPropertyChangedCallback makes you expect that instead of the usual direct binding to the dependency property changes there is actually an additional man-in-the-middle kind of implementation in TextBox that handles how and when TextProperty updates affect and being affected by the DataContext or underlying class properties bound with {Binding} or {x:Bind}.
Note that {x:Bind} and {Binding} are very different mechanisms, especially with the first being a compile-time and the 2nd is run-time, meaning that internally they require different implementation and it's up to the framework developers to make sure they exhibit the identical behavior.
Now in your test scenario you are trying to validate and possibly change a property value in the bound data source expecting that the TextBox will display the value you want, which it does with {x:Bind}, but not with {Binding}.
Apparently you've found a scenario where {x:Bind} and {Binding} implementations behave differently. I did the same tests and totally confirm your findings.

How to detect a moment when the control becomes visible

I am using MVVM. On my View I have a control that by default is hidden, it's Visibility property is Binded to ViewModels property.
<Grid>
<TextBox Visibility={Binding IsVisible, Mode=OneWay, Converter={StaticResource MyVisibilityConverter}}/>
<Grid>
In the ViewModel I have a property
private bool _isVisible;
bool IsVisible
{
get {return _isVisible;}
set {_isVisible = value; NotifyOfPropetyChanged(() => IsVisible);}
}
pretty much straighforward, to show the control I just do
IsVisible = true;
in my ViewModel and the TextBox becomes visible, works fine as intended.
What I want to do is to set Focus on the TextBox just after it becomes visible. The problem is that I can't find any good solution how to determine that this particular control just got visible and it is the moment I can set the focus.
The solution would be to test the visibility inside LayoutUpdated event, but it is definitely not the nicest thing to have in code.
Any better solution?
edit:
To clarify - I don't want to set the focus via MVVM from the ViewModel. There is no problem in setting the focus from the code-behind as it is the UI behaviour. The only problem is how to determine WHEN to do that. There is a some period of time beetween the ViewModel property is set and the layout being updated to match its state. After that perdiod of time I want to be able to catch anything that can notify me "my visibility has changed, now you can change focus"
You could use RegisterPropertyChangedCallback to register a change callback for the Visibility property of the textbox. then in the changed call back method you can set the focus is the visibility is visible.
Put this in the constructor of the code behind:
TextBox1.RegisterPropertyChangedCallback(UIElement.VisibilityProperty, VisibilityChanged);
and add the CallBack method:
private void VisibilityChanged(DependencyObject sender, DependencyProperty dp)
{
if (((UIElement)sender).Visibility == Visibility.Visible)
{
TextBox1.Focus(FocusState.Keyboard);
}
}

Function binding normative

I'm new to WPF and MVVM and am going through an example on Microsoft's site, however, I don't see how the binding is done. In the example linked, there's this piece of code:
public partial class MainPage : UserControl
{
private PartInventoryViewModel viewModel;
public MainPage()
{
InitializeComponent();
viewModel = new PartInventoryViewModel();
this.DataContext = viewModel;
}
private void PartSearchButton_Click(object sender, RoutedEventArgs e)
{
viewModel.GetParts();
}
}
Apparently:
It notifies the ViewModel instance when the user clicks the PartSearchButton.
But how? There's no binding in the XAML above for the PartSearchButton in the example. Is it a normative that if you name your function YourButtonName_Clicked() it will trigger when the button is clicked? Or does it become a listener if you create the function with the (object sender, RoutedEventArgs e) arguments? Or is there some XAML which this tutorial is not showing, where the binding occurs?
Thank you for your answer, sorry if it's a newb one.
Microsoft is not showing all the code that is necessary here. Basically all that this code does is setting the DataContext to a newly instantiated ViewModel. The PartSearchButton_Click is a simple Click-Event-Handler that should look something like this in your XAML-file:
<Button Click="PartSearchButton_Click">Search</Button>
The whole binding thing is happening in these 2 lines of the datagrid in your xaml file:
ItemsSource="{Binding Parts}"
SelectedItem="{Binding CurrentPart, Mode=TwoWay}"
This is telling the DataGrid that it should look for a public property called Parts in the current DataContext. You set the DataContext to a new instance of PartInventoryViewModel, so there needs to be a public property Parts somewhere in there. I guess the PartInventoryViewModel class will be explained a bit further down on the Microsofts site.
The XAML snippets from your link are effectively missing that event handler.
The <source>_<event> guideline is the convention for naming event handlers, but by no means the function gets automatically bound to the corresponding event; you have to add the handler either programmatically or in XAML.
That said, associating application logic to buttons is usually done in WPF by means of commands instead of event handlers. The view model exposes a property of type ICommand, anf the view binds the Command dependency property of a Button (or other controls) to it. How that command is implemented under the hood is completely irrelevant to the view.

C# WPF Switch Resource Programmatically

i`m trying to design a dialog window which will display different messages depending on what case will be true.
so for example when i have a
<CustomDialog Content="{Binding Path=Name}"/>
is there a possibility to change the Binding Path=Name to Path=Whatever or how do you implement something like that ? When the Control should use other resources on runtime.
--------------edit
I ok i`ll try to describe my problem better ;)
I have an ResourceDictionary with strings for example
<System:String x:Key="Message1">Message1</System:String>
<System:String x:Key="Message2">Message2</System:String>
<System:String x:Key="Message3">Message3</System:String>
So when I now call my UserControl
Doing it customdialog.visibility = true; for example
<CustomDialog Text=”” />
I want to define which key from the resourcedictionary is taken when the dialog popups up.
something like customdialog.text = Message1; but Loaded from the ResourceDictionary
is that possible or is there an better way of doing something like this ?
You may provide another content to the same property Name at runtime in code-behind. Suppose you have Initialize (or may be Show) method in your CustomDialog and the last one implements INotifyPropertyChanged:
public class CustomDialog : INotifyPropertyChanged
{
//Your implementation of class goes here
public void Initialize(string message)
{
Name = message;
Visibility = Visibility.Visible;
}
public string Name
{
get {return _name;}
set
{
if (_name != value)
{
_name = value;
raiseOnPropertyChanged("Name");
}
}
}
//Your implementation of class goes here
}
In method Initialize there will be updated Name property and your control will be shown. When there will be setting of Name property must be raise PropertyChanged event which will tell presentation that binded value has updated and to reflect it in the UI.
The easiest way I can think of would be to bind to the parent item, not to a child property, and then use a DataTemplateSelector to select a different template at run-time, depending on some condition involving the bound object or its properties.
Alternatively, if the Content has well defined types, you only need to define DataTemplates with specific data types, and they will be automatically used to display objects of those types.
Not knowing more about the context I can't be much more specific, but if you search for more information on DataTemplates and DataTemplateSelectors you should be fine - you can find a lot of useful information here.

Having trouble deciding how to wire up a UserControl with MVVM

I've been doing the best I can to try to stay true to the separation recommended by the MVVM pattern. One thing I haven't figure out how to do correctly has to do with initializing my UserControls.
My most recent example of this has to do with a library that I wrote to talk to some low-level hardware. That assembly happens to have a UserControl that I can simply drop into any GUI that uses this hardware. All that is necessary for it to work is to set a reference to the object that has access to the low level methods.
However, that's where my problem lies -- currently, the UserControl is added to the GUI via XAML, where I define the namespace and then add the UserControl to my window. Of course, I have no control over its creation at this point, so the default constructor gets called. The only way to set the necessary reference for hardware control involves calling a method in the UC to do so. The ViewModel could feasibly call a method in the Model, e.g. GetController(), and then call the method in the UserControl to set the reference accordingly. The GUI can pass a reference to the UserControl to the ViewModel when said GUI creates the ViewModel, but this violates MVVM because the ViewModel shouldn't know anything about this control.
Another way I could deal with this is to not create the UserControl in XAML, but instead do it all from code-behind. After the ViewModel gets initialized and retrieves an initialized UserControl (i.e. one that has the low-level object reference set), it can set the Content of my Window to the UserControl. However, this also violates MVVM -- is there a way to databind the Content of a Window, TabControl, or any other element to a UserControl?
I'd like to hear if anyone has had to deal with this before, and if they approached it the first or second way I have outlined here, or if they took a completely different approach. If what I have asked here is unclear, please let me know and I'll do my best to update it with more information, diagrams, etc.
UPDATE
Thanks for the responses, guys, but I must not have explained the problem very well. I already use RelayCommands within the UserControl's ViewModel to handle all of the calls to the hardware layer (Model) when the user clicks in the control in the UserControl itself. My problem is related to initially passing a reference to the UserControl so it can talk to the hardware layer.
If I create the UserControl directly in XAML, then I can't pass it this reference via a constructor because I can only use the default constructor. The solution I have in place right now does not look MVVM-compliant -- I had to name the UserControl in XAML, and then in the code-behind (i.e. for the View), I have to call a method that I had added to be able to set this reference. For example, I have a GUI UserControl that contains the diagnostics UserControl for my hardware:
partial class GUI : UserControl
{
private MainViewModel ViewModel { get; set; }
public GUI( Model.MainModel model)
{
InitializeComponent();
ViewModel = new MainViewModel( model, this.Dispatcher);
ViewModel.Initialize();
this.DataContext = ViewModel;
diagnostics_toolbar.SetViewModel( ViewModel);
user_control_in_xaml.SetHardwareConnection( model.Connection);
}
}
where the outer class is the main GUI UserControl, and user_control_in_xaml is the UserControl I had to name in the GUI's XAML.
Looking at this again, I realize that it's probably okay to go with the naming approach because it's all used within the View itself. I'm not sure about passing the model information to user_control_in_xaml, because this means that a designer would have to know to call this method if he is to redo the GUI -- I thought the idea was to hide model details from the View layer, but I'm not sure how else to do this.
You will also notice that the main GUI is passed the Model in the constructor, which I assume is equally bad. Perhaps I need to revisit the design to see if it's possible to have the ViewModel create the Model, which is what I usually do, but in this case I can't remember why I had to create it outside of the GUI.
Am new to MVVM myself but here's a possible solution:
Create a property in your VM that is of the object type (that controls the hardware) and bind it to an attached property on your UserControl. Then you could set the property in your VM using dependency injection, so it would be set when the VM is created. The way I see it, the class that talks to the hardware (hardware controller) is a service. The service can be injected to your view model and bound to your UserControl. Am not sure if this is the best way to do it and if it is strict enough to all the MVVM principles but it seems like a possible solution.
if your question is: How do i show my viewmodel in the view? then my solution is always using viewmodelfirst approach and datatemplates.
so all you have to do is wire up your viewmodel via binding to a contentcontrol.content in xaml. wpf + datatemplates will do the work and instantiate your usercontrol for your viewmodel.
You are right, the ViewModel shouldn't know about anything in the View - or even that there is such a thing as a View, hence why MVVM rocks for unit testing too as the VM couldn't care less if it is exposing itself to a View or a test framework.
As far as I can see you might have to refactor things a little if you can. To stick to the MVVM pattern you could expose an ICommand, the ICommand calls an internal VM method that goes and gets the data (or whatever) from the Model, this method then updates an ObservableCollection property of the data objects for the View to bind to. So for example, in your VM you could have
private ICommand _getDataCommand;
public ICommand GetDataCommand
{
get
{
if (this._getDataCommand == null)
{
this._getDataCommand = new RelayCommand(param => this.GetMyData(), param => true);
}
return this._getDataCommand;
}
}
private void GetMyData{
//go and get data from Model and add to the MyControls collection
}
private ObservableCollection<MyUserControls> _uc;
public ObservableCollection<MyUserControls> MyControls
{
get
{
if (this._uc == null)
{
this._uc = new ObservableCollection<MyUserControls>();
}
return this._uc;
}
}
For the RelayCommand check out Josh Smiths MSDN article.
In the View you could either call the ICommand in the static constructor of your UC - I am guessing youwould need to add an event in your class for this - or call the ICommand from some sort of click event on your UC - maybe just have a 'load' button on the WPF window. And set the databinding of your UC to be the exposed observable collection of the VM.
If you can't change your UC at all then you could derive a new class from it and override certain behaviour.
Hope that helps a bit at least, like I say, have a look at Josh Smiths MVVM article as he covers the binding and ICommand stuff in there brilliantly.
If you set the DataContext of the Window or UserControl containing thisUserControl to the main view model, the user control can call SetHardwareConnection() on itself in its Loaded event (or DataContextChanged event handler).
If that's not possible because you're saying the UserControl is 'fixed', you should derive from it or wrap it up in another UserControl, which would serve as a MVVM 'adapter'.
(In order to bind the window: you could make the MainViewModel a singleton with a static Instance property and use DataContext="{x:Static MyClass.Instance}". A nice way to get things going quickly)
Note; this is based on my understanding that MVVM works because of Bindings.. I always bind the control to a ViewModel, not pass a ViewModel as a parameter.
Hope that helps!

Categories

Resources