I have a simple WPF page with a couple of RadioButtons, each RadioButton is registered with a Checked event handler so that when the selection is changed something can happen. By default I want to have one of these RadioButtons selected, so I have set the IsChecked property to True in the xaml. Something like this:
<RadioButton Checked="Radio_Checked" IsChecked="True">One</RadioButton>
<RadioButton Checked="Radio_Checked">Two</RadioButton>
The problem with this is that during InitializeComponent the IsChecked property causes the event to fire, this causes a null reference exception because my event handler attempt to use elements that have not been initialized yet.
Currently I have gotten around the issue by checking if the page IsInitialized within my handler as follows:
private void Radio_Checked(object sender, RoutedEventArgs e)
{
if (this.IsInitialized)
{
if(MyRadioButton.IsChecked.GetValueOrDefault())
{
//SomeOtherElement is not initialized yet so it is null
SomeOtherElement.Visibility = Visibility.Visible;
}
}
}
I would like to avoid having to use if (this.IsInitialized) in all my event handlers, as this is something I never had to do in WinForms.
So my question is, can I handle this a different way without having to add extra code to all my event handers?
To be honest, I'm surprised that you aren't checking for null in your handlers anyway... checking for IsInitialised is just a slight variation on checking for null. Handling null values is just part of good programming and let's face it, it's not really adding a lot of code.
So to answer your question, I would say 'No, there is no way around checking for null (or IsInitialised) in your event handlers if you don't want NulReferenceExceptions to occur'.
However, when using the MVVM methodology, we don't use many events, preferring instead to use data binding and ICommand instances where possible. When we do need to use events, we generally use them in Attached Properties, but there you will still need to check for null values.
You can remove the event handler from the xaml and add it after InitializeComponent();
radioButton1.Checked+=Radio_Checked;
Every element is created in that order it is in your XAML.
<RadioButton x:Name="MyRadioButton" ...>
<YourElement x:Name="SomeOtherElement" ...>
I assume in your XAML the RadioButton is placed before the other element you are referencing. On creation of an element in InitializeComponent all properties are set and also all events are fired. So SomeOtherElement does not exist in that moment.
The solution is quiet simple:
<YourElement x:Name="SomeOtherElement" ...>
<RadioButton x:Name="MyRadioButton"...>
Set SomeOtherElement before your RadioButtons.
If there are reasons to not switch the order of your elements in your XAML, then use the already mentioned null check:
if (SomeOtherElement != null)
{
SomeOtherElement.Visibility = Visibility.Visible;
}
Related
There are many related questions on the net, addressing the apparently same issue; however, none of the answers was helpful to me.
The basic scenario in short:
I have a dialog containing a ComboBox and a Reset-Button. The ComboBox has a Binding to the ViewModel. When the button is clicked, I'd like to update the item in the ComboBox. This does not work.
<ComboBox x:Name="EditorFonts" SelectedValue="{Binding FontValue, Mode=TwoWay}" />
The obvious answer or counter-question is: do I used OnNotifyPropertyChanged? Well, yes, basically I do, the following method is being called...
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
However, PropertyChanged is NULL; thus, the if-block is not raised and nothing happens.
I also found on the net that there might be a problem with SelectedValue, and I should try using SelectedItem instead. This doesn't help either.
And even if I update the ComboBox value directly (and do not use Source-to-Target-Binding), the view isn't updated.
At this point I need to add some further details about the implementation that I, unfortunately, cannot provide as code. For one it's way to complex and for another I do not all code parts. Here's a brief explanation:
The dialog box itself is provided by another team. They own the Reset-Button. And they provide an interface. When implementing this interface and using MEF Exports, my subpage will be shown in the dialog and I get notified when the Reset-Button is clicked. This all is implemented in another assembly and I do not know a lot about their implementation.
So, when I get the notification about the Reset-Button, I try to refresh the value as described above. As this didn't work, I put an own Reset-Button (let's call it Reset2) next to my ComboBox. When Reset2 is triggered, I raise the very same code and now the ComboBox is being updated. Also, the PropertyChanged is not NULL.
My conclusion is, that the implementation of my DataBinding cannot be wrong. Actually, when closing the dialog and opening it again (after using Reset1), the ComboBox represents the correct value.
Furthermore, it doesn't matter to which property I bind.
I tried any of the following mechanisms to refresh the ComboBox but to no success.
BindingExpression be = Value.GetBindingExpression(ComboBox.SelectedValueProperty);
be.UpdateTarget();
EditorFonts.Dispatcher.Invoke(emptyDelegate, DispatcherPriority.Render);
EditorFonts.InvalidateVisual();
EditorFonts.InvalidateProperty(ComboBox.SelectedValueProperty);
EditorFonts.InvalidateMeasure();
EditorFonts.InvalidateArrange();
EditorFonts.UpdateLayout();
I'm pretty sure that once I found out why the PropertyChanged value is NULL, the ComboBox will be updated properly. However, I've got absolutely no idea how to fix this issue.
So, does anyone have any guesses on what's might be going wrong? What else I can look for, as the trivial issues regarding DataBinding (like not calling OnPropertyChanged) are not the cause.
I tried a dirty "hack" which is whenever Reset1 is clicked, I raise an event on Reset2.Clicked:
Reset2.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
Then PropertyChanged does indeed not equal NULL, but–funny enough–the ComboBox is still not updated.
I don't not for what purpose I tried that, but I was sure that it would succeed, so I'm more confused now about why the ComboBox refuses to show what I'd like to see.
When using the INotifyPropertyChanged interface, it is customary to see code like this:
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
What this means in plain English is this:
If there are any event handlers attached to the PropertyChanged event, then call them with this PropertyChangedEventArgs object
Therefore, if handler is null, then no handlers have been attached to that event.
I've faced with an issue, that blows up my mind.
Let's look at these methods from ButtonBase:
private void HookCommand(ICommand command)
{
CanExecuteChangedEventManager.AddHandler(command, OnCanExecuteChanged);
UpdateCanExecute();
}
private void OnCanExecuteChanged(object sender, EventArgs e)
{
UpdateCanExecute();
}
private void UpdateCanExecute()
{
if (Command != null)
{
CanExecute = MS.Internal.Commands.CommandHelpers.CanExecuteCommandSource(this);
}
else
{
CanExecute = true;
}
}
HookCommand is called, when you're assigning new command to the button. It subscribes to CommandManager.RequerySuggested via weak event manager and updates button state (enabled/disabled).
OnCanExecuteChanged is just an event handler, and UpdateCanExecute ultimately calls your ICommand.CanExecute, when you use something different from RoutedCommand. This is the case, when you're working with any MVVM framework.
Now, the problem.
One of my data templates is applied to the ContentControl to show some data:
<ContentControl Grid.Row="0" Content="{Binding}" ContentTemplate="{StaticResource TemplateState}"/>
This template is contained within rather complex visual tree inside another ContentControl, which is hosted in ElementHost (this is a WPF component in WinForms MDI-application).
There are a couple of buttons inside this template, whose Command properties are bound to RelayCommands.
When I close MDI child, which contains visuals, rendered using this data template, buttons try to update their state and call OnCanExecuteChanged. This is a big problem, because CanExecute calls some disposable object, that already had been disposed.
I know, that:
1) the window (WinForms form) is closed at this moment, because CanExecute is called after the Form.Closed event is handled;
2) there's no memory leaks - if I mock CanExecute, memory profiler shows, that my view model, which contains commands, is collected by GC and no longer exists.
The question.
What's the purpose to check CanExecute, if the button isn't visible?
Is there any option to prevent this behavior?
P.S.
The only workaround I see is to keep somewhere in my view models a flag,, which will show, that disposable was disposed, and return false from CanExecute.
Any better ideas?
I would give four potential answers, and one guess towards why it is implemented the way it is:
Set the DataContext of the window which is getting torn down to null before you dispose everything. The button would have no reference on the object, so the exception never gets thrown.
Wrap the call to the disposable object in a try/catch which filters ObjectDisposedException and returns false.
Add a IsDisposed property to the disposable object, and check beforehand. It does seem like there could be a race condition here if you are doing anything on a non-UI or finalizer thread.
Hold a WeakReference to the disposable object if you are waiting for the finalizer to call Dispose, or set the reference to null after you call Dispose() and check if it is null prior to calling upon it.
As far as why command gets queried even when non-visible, consider that the result of the command may control the visibility. Imagine the following:
<!-- This would probably have to be done in some more complicated way, like
passing IsEnabled to a converter with CanExecute as the parameter, or
by just binding to IsEnabled. -->
<Button Visibility="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=CanExecute}"
Command="{Binding TheCommand" Content="Do it" />
If it did not query the state of the button is hidden, it would never be shown once disabled.
This article states that Page_PreInit should be used to
create or re-create dynamic controls.
For example:
Button button = new Button();
somePanel.Controls.Add(button);
Good. I understand.
However, it also says:
If the request is a postback, the values of the controls have not yet
been restored from view state. If you set a control property at this
stage, its value might be overwritten in the next event.
Huh?
Does this mean that all I should do is create the button, but not set any members of the button?
For example:
Button button = new Button() { CommandArgument="arg" };
somePanel.Controls.Add(button);
Does this mean that setting CommandArgument in this event is incorrect/not recommended/might cause an error/unexpected behavior?
Assuming it is incorrect, this would lead me to think that one would have to do something like this:
protected void Page_PreInit(object sender.....)
{
somePanel.Controls.Add((new Button());
}
protected void Page_Init(object sender.....)
{
foreach(Button button in somePanel.Controls)
button.CommandArgument = "arg";
}
is this the right way?
Finally, in which event should one set dynamic control properties?
There is no single answer for that last question as depending on the nature of the property it may or may not make sense to set a value in a specific method.
If the request is a postback, the values of the controls have not yet
been restored from view state. If you set a control property at this
stage, its value might be overwritten in the next event.
Might is the keyword here. If you consider some properties that may change as a form goes through various states then this is where you have to be careful of what may get overwritten as well as the question of whether or not this is a bad thing as it may be that the updated value should persistent and in other cases the original value may be better such as if someone wants to reset the form to its initial state.
My suggestion would be to do some trial and error to see what works as I can remember working with dynamic controls that could be tricky in some me cases to manage properly.
I have a CheckBox that, when checked/unchecked will toggle the Enabled property of some other controls. I did have my code looking something like this:
checkBox.CheckedChanged += new EventHandler((o, e) =>
{
control1.Enabled = checkBox.Checked;
control2.Enabled = checkBox.Checked;
});
But today I started playing with DataBindings and discovered I could do this:
control1.DataBindings.Add("Enabled", checkBox, "Checked");
control2.DataBindings.Add("Enabled", checkBox, "Checked");
They seem to behave the same, but I suspect one is preferred over the other. Or perhaps one has some unexpected behavior that may trip me up later.
Is one way better than the other?
The first one is checked at compiled time, so I'd go with that one. I assume that if the "Enabled" property in the second example was not valid you would get a runtime error.
You should notice that there is another difference:
with data binding (method 2), if the object implements INotifyPropertyChanged, and if the object.Enabled is changed outside the UI layer, the checkbox.checked state will get changed automatically.
I am trying to load a preferences window for my application and I would like the apply button to initially be disabled, then when a preference is updated, the apply button gets enabled again. I have some controls data bound to a preferences object and what happens is that after the window loads, the combobox events get triggered. Is there any event that is guaranteed to happen dead last after everything is stable?
Here is what my code looks like (the apply button is always enabled after the window loads):
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_preferencesData = new PreferencesDataContext();
LayoutRoot.DataContext = _preferencesData;
ButtonApply.IsEnabled = false;
}
private void ComboBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
ButtonApply.IsEnabled = true;
}
Is it also interesting to note that this only happens with textboxes and comboboxes, not checkboxes or radiobuttons.
Best solution for simple need
Joseph's answer is the best solution by far for your simple need: Just use data binding and let the data model handle it.
Answer to question as posed
There are more complex scenarios when you really do need control after absolutely everything has finished loading and all events have fired. There is no single event that occurs "dead last", but it is easy to effectively roll your own using the Dispatcher queue.
This is how to do it:
Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(() =>
{
var x = ComputeSomething(1, 2, 3);
DoSomething(x, "Test");
}));
Everything inside the { } will be executed when WPF finishes everything at a higher priority than ContextIdle, which includes all event handlers, loaded events, input events, rendering, etc.
Sequence of events when a Window is created and shown
As requested, here is the sequence of major events in WPF when a window is created and shown:
Constructors and getters/setters are called as objects are created, including PropertyChangedCallback, ValidationCallback, etc on the objects being updated and any objects that inherit from them
As each element gets added to a visual or logical tree its Intialized event is fired, which causes Styles and Triggers to be found applied in addition to any element-specific initialization you may define [note: Initialized event not fired for leaves in a logical tree if there is no PresentationSource (eg Window) at its root]
The window and all non-collapsed Visuals on it are Measured, which causes an ApplyTemplate at each Control, which causes additional object tree construction including more constructors and getters/setters
The window and all non-collapsed Visuals on it are Arranged
The window and its descendants (both logical and visual) receive a Loaded event
Any data bindings that failed when they were first set are retried
The window and its descendants are given an opportunity to render their content visually
Steps 1-2 are done when the Window is created, whether or not it is shown. The other steps generally don't happen until a Window is shown, but they can happen earlier if triggered manually.
The Window.ContentRendered event fulfilled my requirements.
I just did kind of the same thing behaviorly in a systray WPF app.
However, I didn't do it using event handling. I simply bound the Enabled property of my button to a property in my ViewModel, and had the property updated whenever I needed the behavior.
You can use ManagedSpy to figure this out on your own.
http://msdn.microsoft.com/en-us/magazine/cc163617.aspx
Setting the DataContext will likely fire the SelectionChanged event, and you can't rely on when exactly it's fired. Some logic checking on what exactly is selected would be more reliable:
private void ComboBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if (myComboBox.SelectedItem == null)
{
buttonApply.IsEnabled = false;
}
else
{
buttonApply.IsEnabled = true;
}
}
The reason it's happening afterwards with your code as-is is because the event gets queued on the thread for the UI, so it's up to Windows if it will execute the next line of code in Load, or to handle the other events on the queue.
Not to throw a whole lot of stuff at you that you may or may not be familiar with, but if this is a relatively new codebase, you may want to consider using the MVVM pattern and use Commands instead of the archaic (emphasis mine) eventing model.
Order of Events in Windows Forms
Control.HandleCreated
Control.BindingContextChanged
Form.Load
Control.VisibleChanged
Form.Activated
Form.Shown