Custom TypeDescriptor and AddValueChange/OnValueChanged - c#

I’m exploring the use of custom TypeDescriptors to intercept when a data-bound property on a WPF control sets a CLR property. Due to reasons I need to figure out which exact control is setting a value.
If I use INotifyPropertyChanged I’ll get all of the different controls that subscribe to any property in my view model, not all that helpful. So I figured I’d go down the custom TypeDescriptor path and try the ProperyDescriptor.AddValueChanged and PropertyDescriptor.OnValueChanged way.
I can see that WPF calls ProperyDescriptor.AddValueChanged as expected, but my call to ProperyDescriptor.OnValueChanged never seems to trigger a call to PropertyDescriptor.GetValue and an update in the UI.
Are there some gotchas to using this stuff that I’m missing? Or are there other ways to figure out who’s subscribing to a CLR property. My alternative as I see it now would be to emit a custom proxy for the whole shebang, but I’d very much like to not have do that.
Edit: On looking at bit closer, I noticed that the delegates I get through PropertyDescriptor.AddValueChanged go to some internal MS helper class, so without some unreliable reflection-fu there's no way to use that to get the source control. I think I'll have to go with a custom proxy with dependency properties.

There's an alternative that may be useful. In a binding, you can set the NotifyOnSourceUpdated to True, which will raise the control's SourceUpdated event when WPF updates the CLR property it's data-bound to. For example, in a TextBox you can write:
<TextBox
Text="{Binding MyText, NotifyOnSourceUpdated=True}"
SourceUpdated="TextBox_OnSourceUpdated"
/>
In the code-behind, you can get the exact control (the sender) and the name of the dependency property that updated the CLR property:
private void TextBox_OnSourceUpdated(object sender, DataTransferEventArgs e)
{
var control = (TextBox)sender;
var propertyName = e.Property.Name;
}
With this method, you need to know that the control was a TextBox, but you can probably update it to test different kinds of controls. It depends on what you need this for, really.

Related

Prism.WPF: Change MainWindow Window.Effect on Navigation

The application is a Prism Application in WPF using C#.
I am attempting to assign a BlurEffect to the Window.Effect property when a button is clicked on the navigation menu.
I have the Window.Effect bound to a property in my viewmodel.
<Window ... other properties ..
Effect = {Binding Fuzzy}>
and the Fuzzy property in the ViewModel.
private Effect _fuzzy;
public Effect Fuzzy { get => _fuzzy; set => SetProperty(ref _fuzzy, value); }
What I am attempting to implement is that when a button is clicked on the navigation menu that the window will blur while a UserControl is loading.
I have tried to implement the change in the Navigate method.
private void Navigate(string viewName)
{
PerformBlur();
_regionManager.RequestNavigate("ContentRegion", viewName);
}
private void PerformBlur()
{
BlurEffect blur = new BlurEffect();
blur.Radius = 4;
var ef = blur;
_fuzzy = ef; //I've tried Fuzzy = ef too
}
But that doesn't work.
I need to make the change to the window effect before it attempts to navigate, and I haven't been able to figure out how to make that happen. I have a feeling that the easiest way to do this would be to use a click event rather than a command, and then call the command in the viewmodel from the codebehind. However, that doesn't seem to be the proper implementation when using MVVM. Any suggestions on how to implement this functionality would be greatly appreciated.
(Bonus points if you can tell me how to animate the blur. lol)
I have a feeling that the easiest way to do this would be to use a click event rather than a command, and then call the command in the viewmodel from the codebehind. However, that doesn't seem to be the proper implementation when using MVVM.
Invoking the command programmatically from the code-behind of the view is not any worse than invoking it from the XAML markup of the very same view as far as MVVM is concerned.
MVVM is not about eliminating code from the views. It's about separation of concerns. You can implement an entire view programmtically in a C# without using XAML at all and still be fully compliant with MVVM.
Trying to do fairly complex stuff in XAML just because you possible can is generally considered as an antipattern. Remember that XAML is a markup language. C# is a much more expressive and concise language so if you can solve your issue by writing some code, then this is most probably what you should do.
Taking a look at the prism source code, I can see iregionmanager is full of abstracted interfaces.
( Wow. I don't know why it still surprises me but prism is very complicated ).
https://github.com/PrismLibrary/Prism/blob/master/src/Wpf/Prism.Wpf/Regions/IRegion.cs
That includes IRegionNavigationService
https://github.com/PrismLibrary/Prism/blob/master/src/Wpf/Prism.Wpf/Regions/IRegionNavigationService.cs
You could therefore override pretty much any functionality you like, if you wanted to.
Notice though, the two events :
/// <summary>
/// Raised when the region is about to be navigated to content.
/// </summary>
event EventHandler<RegionNavigationEventArgs> Navigating;
/// <summary>
/// Raised when the region is navigated to content.
/// </summary>
event EventHandler<RegionNavigationEventArgs> Navigated;
Looks to me like "all" you need is a reference to your region navigation service in the view.
Handle those two events to set blur then remove blur.
You could then do navigation in code behind or viewmodel. Whichever suits.
If you wanted to decouple viewmodel from view, you could use the eventaggregator.
There is another option though.
You don't explain exactly what you have there. So let's imagine and consider a better way to do this.
Say you have a set content of a set control you're always navigating. That's being switched out as you navigate for a new view whose datacontext is a new viewmodel.
You could bind an attached property from the window to the datacontext of that.
In that property you can have a change callback.
In a base viewmodel you could add an IsLoaded bool property which is initialy false.
When your dependency property callback returns null or false then you blur.
You change the viewmodel property to false in the current viewmodel when you start to navigate. The window blurs. The content is switched out and you get a new viewmodel. Once navigation completes you set that ILoaded true. You callback un blurs the window.

Winforms Databinding with a custom setter

I'm using some good old fashing DataBinding in a Winforms project.
I have my form with a control (A devExpress RichTextEdit for those that want to know)
I want to bind the HtmlText property of the richTextEdit control to a property on my ViewModel
I have done that binding and that is not a problem. However I have realised that the HtmlText that comes out of the richTextEdit is HtmlEncoded. Meaning that characters get encoded into their html entity representation.
eg < becomes < etc
I don't want this to happen as those tags have special meaning further down the line and I need to keep them.
So in my ViewModel that has all the notify property changed stuff and essentially wraps my domain object I could do this
public class ViewModel: INotifyPropertyChanged
{
public string WrappedProperty
{
get => domainObject.Property;
set
{
domainObject.Property = HttpUtility.DecodeHtml(value);
//Raise Property changed event etc
}
}
}
and in my form I create a Data binding
Binding binding = new Binding("HtmlText", _viewModel, "WrappedProperty", true, DataSourceUpdateMode.OnPropertyChanged,null,null);
_richEditControl.DataBindings.Add(binding);
now this works as intended, however I don't like it. My view model is doing things because of the control I am currently using. Its 'leaky' and it smells.
I want my View to be handle view specific issues.
What I'd like to do is to create a binding between the controls Html Text property and my View models WrappedProperty property, providing a custom function to be used when setting the property
from the control into the view model. Is is something that can be implemented or is there some kind of common work around pattern that I am missing?
Thanks
You can handle this in the binding using the Parse event.
Binding binding = new Binding("HtmlText", _viewModel, "WrappedProperty", true, DataSourceUpdateMode.OnPropertyChanged,null,null);
binding.Parse += (sender, e) => e.Value = HttpUtility.DecodeHtml(e.Value);
_richEditControl.DataBindings.Add(binding);
I managed to discover this myself, but as I struggled to find anything for a while on google about this I thought'd I'd myself and hopefully help future developers
There is an event on a Binding called Parse. Subscribing to this event allows to you to work with the value before it gets sent back to the data source.
Its partner is the Format event this allows you to work the value before it is displayed in the control
https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.binding.parse?view=netframework-4.8

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.

Is there any unique identifier for wpf UIElement?

For logging user actions in my WPF forms, I added some global event handlers
I want to log exactly which control fire the event, is there some unique identifier for a wpf UIElement like ClientId in ASP.Net?
Why don't you use the Hash Code.
You can compare the values to make sure they are the same object, and its easy to get them with .GetHashCode()
Edit
Obviously this is different every time you run the program, so actually this is prolly a bad idea, unless you want to update the log each time the process is logged. Still possible though
I mean you could store a hash value for each object at the time the log is created, but i don't know if I like that
Seems I found answer to my question, the answer is No, now way to do that, As noted in MSDN here (http://msdn.microsoft.com/en-us/magazine/dd483216.aspx)
Notice that the top-level Window control definition does not contain a
Name attribute. This is significant because, as we'll see shortly,
when you write test automation, an easy way to get a reference to a
control using the MUIA library is to access the AutomationId property,
which is generated by the compiler from the control's Name attribute.
Controls without a XAML Name attribute will not receive an
AutomationId property. This idea is a specific, low-level example of
the importance of considering application design issues for things
such as security, extensibility, and test automation.
One way you can do this is with a custom attribute. Like so...
The UIElement you want to log (UserControl for example):
[UserInterfaceID(ID = "{F436E9B3-C2F6-4CF8-8C75-0A2A756F1C74}")]
public partial class MyUserControl : UserControl
{
InitializeComponent();
// or whatever...
}
Then you need the custom attribute class
[System.AttributeUsage(AttributeTargets.Class)]
public class UserInterfaceIDAttribute : Attribute
{
public Guid ID { get; set; }
}
Now in your code, you can do something like this:
MyUserControl control = new MyUserControl();
foreach(object att in control.GetCustomAttributes(typeof(UserInterfaceAttribute),false))
{
UserInterfaceAttribute uiAtt = (UserInterfaceAttribute)att;
Guid theID = uiAtt.ID;
}
Because you are tagging the control with an attribute in the code, the unique identifier never changes no matter how many times you kill / launch the application.
Of course this is a basic example that shows how to access the ID but you will probably want to use some type of Aspect Oriented Programming. I do exactly this kind of thing using Castle Windsor Interceptors, but that is out of the scope of this post.
Ideally, you will be accessing this ID when there is some kind of event that gets fired. Using interceptors allows you go capture method calls before they are invoked wherein you can look up the ID as shown above and log the action. Alternatively, you can just use
this.GetCustomAttributes(...)
in some method when an event is fired on the control and embed your Logging code there. This pattern is not the best because you're sprinkling cross-cutting concerns all over making some type of Aspect Oriented Programming approach better...but again I digress and it is out of the scope of this post...but you get the idea.
Hope this helps.
You're just looking at adding a Name or x:Name so that the Window/UserControl/Page exposes the control to the rest of the class with the specified name.
<Window ...>
<Grid>
...
<!-- These controls are named, so you can access them directly by name -->
<Button x:Name="btnMyNamedButton" ... />
<Button Name="btnMyOtherNamedButton" ... />
<!-- This control is not named, so you can not directly access it by name -->
<Button ... />
<Grid>
</Window>
public partial class MyWindow : Window
{
public MyWindow()
{
InitializeComponent();
//btnMyNamedButton can be accessed
//btnMyOtherNamedbutton can also be accessed
//The third button can be accessed, but not directly by name.
}
}
Also, you can always use the FrameworkElement.Tag object. It's meant to store arbitrary information, so you can use this as a unique identifier if you want to.
I believe, for logging user actions, you can use the UIAutomation tree and the AutomationElement.AutomationId property, because this approach is supported in all standard UI controls by default. Many third-party controls are also supports the AutomationId for their elements (e.g. grid cells). An AutomationId is useful for creating test automation scripts.
see FrameworkElement.Tag Property
The Tag property can be used. It is of type object and can be set to anything.
I think UIElement.Uid should work. Assign it in XAML and access it in code.
<Label Uid="FirstName"/>
OR in a style property
<Setter Property="Uid" Value="SecondName"/>
Then refer in C# code:
if (Keyboard.FocusedElement is UIElement uiElement)
{
Debug.WriteLine(this, $"{uiElement.Uid}");
}

How not to lose binding source updates?

Suppose I have a modal dialog with a textbox and OK/Cancel buttons. And it is built on MVVM - i.e. it has a ViewModel object with a string property that the textbox is bound to.
Say, I enter some text in the textbox and then grab my mouse and click "OK". Everything works fine: at the moment of click, the textbox loses focus, which causes the binding engine to update the ViewModel's property. I get my data, everybody's happy.
Now suppose I don't use my mouse. Instead, I just hit Enter on the keyboard. This also causes the "OK" button to "click", since it is marked as IsDefault="True". But guess what? The textbox doesn not lose focus in this case, and therefore, the binding engine remains innocently ignorant, and I don't get my data. Dang!
Another variation of the same scenario: suppose I have a data entry form right in the main window, enter some data into it, and then hit Ctrl+S for "Save". Guess what? My latest entry doesn't get saved!
This may be somewhat remedied by using UpdateSourceTrigger=PropertyChanged, but that is not always possible.
One obvious case would be the use of StringFormat with binding - the text keeps jumping back into "formatted" state as I'm trying to enter it.
And another case, which I have encountered myself, is when I have some time-consuming processing in the viewmodel's property setter, and I only want to perform it when the user is "done" entering text.
This seems like an eternal problem: I remember trying to solve it systematically from ages ago, ever since I've started working with interactive interfaces, but I've never quite succeeded. In the past, I always ended up using some sort of hacks - like, say, adding an "EnsureDataSaved" method to every "presenter" (as in "MVP") and calling it at "critical" points, or something like that...
But with all the cool technologies, as well as empty hype, of WPF, I expected they'd come up with some good solution.
At critical points, you can force the binding to push through to your view model:
var textBox = Keyboard.FocusedElement as TextBox;
BindingOperations.GetBindingExpression(textBox, TextBox.TextProperty).UpdateSource();
Edit:
OK, since you don't want hacks we have to face the ugly truth:
In order to implement a clean view, the properties exposed by your view model should be friendly to frequent binding updates.
An analogy we can use is a text editor. If the application was a giant text box bound to a file on disk, every keystroke would result in writing the whole file. Even the concept of saving is not needed. That's perversely correct but terribly inefficient. We all immediately see that the view model needs to expose a buffer for the view to bind to and this re-introduces the concept of save and forces state handling in our view model.
Yet, we see this is still not efficient enough. For even medium-sized files the overhead of updating the whole-file buffer on every keystroke becomes unbearable. Next we expose commands in our view model to efficiently manipulate the buffer, never actually exchanging the whole buffer with the view.
So we conclude that in order to achieve efficiency and responsiveness with pure MVVM, we need to expose an efficient view model. That means that all text boxes can be bound through to properties with no ill effects. But, it also means that you have to push state down into the view model to handle that. And that's OK because a view model is not the model; it's job is it to handle the needs of the view.
It's true that we can rapidly prototype user interfaces by taking advantage of shortcuts like binding on focus changes. But binding on focus changes can have negative consequences in real applications and if so then we should simply not use it.
What is the alternative? Expose a property friendly to frequent updates. Call it the same thing as the old inefficient property was called. Implement your fast property using the slow property with logic that depends on the state of the view model. The view model gets the save command. It knows whether the fast property has been pushed through to the slow property. It can decide if when and where the slow property will be synched to the model.
But you say, haven't we just moved the hack from the view to the view model? No, we have lost some elegance and simplicity, but go back to the text editor analogy. We have to solve the problem, and it is the view model's job to solve it.
If we want to use pure MVVM and we want efficiency and responsiveness, then lame heuristics like let's avoid updating the binding source until the element loses focus won't help. They introduce as many problems as they solve. In that case, we should let the view model do its job, even if means adding complexity.
Assuming we accept it, how can we manage the complexity? We can implement a generic wrapper utility class to buffer the slow property and allow the view model to hook its get and set methods. Our utility class can auto-register for save command events to reduce the amount of boilerplate code in our view model.
If we do it right, then all the parts of the view model that were fast enough to be used with property changed binding will all still be the same, and the others that were worthy of asking the question "Is this property too slow?" will have a small amount of code to address the issue, and the view is none the wiser.
This is a tricky one and I agree a non-hack and more-or-less code free solution should be found. Here are my thoughts:
The view is responsible because it sets IsDefault to true and allows for this 'problem'
The ViewModel should not be responsible in any way to fix this it might introduce dependencies from VM to V and thus breaking the pattern.
Without adding (C#) code to the View all you can do is either change bindings (e.g. to UpdateSourceTrigger=PropertyChanged) or add code to a base class of the Button. In the base class of the button you might be able to shift focus to the button before executing the command. Still hack-ish but cleaner than adding code to the VM.
So at the moment the only 'nice' solutions I see require the view developers to stick to a rule; set the binding in a specific way or use a special button.
I would add a Click event handler for the default button. The button's event handler is executed prior the command will be called, so the data bindings can be updated by changing the focus in the event handler.
private void Button_Click(object sender, RoutedEventArgs e) {
((Control)sender).Focus();
}
However, I don't know if similar approach can be used with other shorcut keys.
Yes, I have quite some experience. WPF and Silverlight still have their pain areas. MVVM doesn't solve it all; it is not a magic bullet and the support in the frameworks is getting better but still lacks. E.g., I still find editing deep child collections a problem.
At the moment I handle these situations case by case because a lot depends on the way the individual view have to work. This is what I spend most of my time on because I generate a lot of plumbing using T4 so I have time left for these quirks.
The problem is that the TextBox's text has a default source trigger of LostFocus instead of PropertyChanged. IMHO this was a wrong default choice since it is quite unexpected and can cause all sorts of problems (such as the ones you describe).
The simplest solution would be to always explicitly use UpdateSourceTrigger=PropertyChanged (as the others suggested).
If this isn't feasible (for whatever reason), I would handle the Unloaded, Closing or Closed events and manually update the binding (as shown by Rick).
Unfortunately it seems that certain scenarios are still a bit problematic with a TextBox, so some workarounds are necessary. For example, see my question. You might want to open a Connect bug (or two) with your specific problems.
EDIT:
Pressing Ctrl+S with focus on the TextBox, I would say the behavior is correct. After all, you are executing a command. This has nothing to do with the current (keyboard) focus. The command may even depend on the focused element! You are not clicking on a button or similar, which would cause the focus to change (however, depending on the button, it may fire the same command as before).
So if you want to only update the bound Text when you lose focus from the TextBox, but at the same time you want to fire a command with the newest contents of TextBox (i.e. the changes without it having lost focus), this does not match up. So either you have to change your binding to PropertyChanged, or manually update the binding.
EDIT #2:
As for your two cases why you cannot always use PropertyChanged:
What precisely are you doing with StringFormat? In all my UI work so far I use StringFormat to reformat data I am getting from the ViewModel. However, I am not sure how using StringFormat with data which is then again edited by the user should work. I am guessing you want to format the text for display, and then "unformat" the text the user enters for further processing in your ViewModel. From your description, it seems it isn't "unformatted" correctly all the time.
Open a Connect bug where it isn't working as it should.
Write your own ValueConverter which you use in the binding.
Have a separate property with the last "valid" value and use that value in your ViewModel; only update it once you get another "valid" value from the property you use in databinding.
If you have a long-running property setter (ex. a "validate" step), I would do the long-running part in a separate method (getters and setters should normally be relatively "fast"). Then run that method in a worker thread/threadpool/BackgroundWorker (make it abortable so you can restart it with a new value once the user enters more data) or similar.
What you think about proxy command and KeyBinding to ENTER key?
EDITED:
There we have one utility command (like converter), which requires knowledge about concrete view. This command can be reused for any dialog with same bug. And you add this functionality/hack only in view where this bug exists, and VM will be clear.
VM creates to adapt business to view and must provide some specific functionality like data conversion, UI commands, additional/helper fields, notifications and hacks/workarounds. And if we have leaks between levels in MVVM we have problems with: high connectivity, code reuse, unit testing for VM, pain code.
Usage in xaml (no IsDefault on Button):
<Window.Resources>
<model:ButtonProxyCommand x:Key="proxyCommand"/>
</Window.Resources>
<Window.InputBindings>
<KeyBinding Key="Enter"
Command="{Binding Source={StaticResource proxyCommand}, Path=Instance}"
CommandParameter="{Binding ElementName=_okBtn}"/>
</Window.InputBindings>
<StackPanel>
<TextBox>
<TextBox.Text>
<Binding Path="Text"></Binding>
</TextBox.Text>
</TextBox>
<Button Name="_okBtn" Command="{Binding Command}">Ok</Button>
</StackPanel>
There used special proxy command, which receive element (CommandParameter) to move focus and execute. But this class requires ButtonBase for CommandParameter:
public class ButtonProxyCommand : ICommand
{
public bool CanExecute(object parameter)
{
var btn = parameter as ButtonBase;
if (btn == null || btn.Command == null)
return false;
return btn.Command.CanExecute(btn.CommandParameter);
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
if (parameter == null)
return;
var btn = parameter as ButtonBase;
if (btn == null || btn.Command == null)
return;
Action a = () => btn.Focus();
var op = Dispatcher.CurrentDispatcher.BeginInvoke(a);
op.Wait();
btn.Command.Execute(btn.CommandParameter);
}
private static ButtonProxyCommand _instance = null;
public static ButtonProxyCommand Instance
{
get
{
if (_instance == null)
_instance = new ButtonProxyCommand();
return _instance;
}
}
}
This is just idea, not complete solution.

Categories

Resources