Calling UIElement Command from code behind - c#

I am trying to execute a bound command from my code behind utilizing the UiElement. button.Command.Execute(button.CommandParameter)
However, at this point the Command property of the button is null. simultaneously when I check the command in my View Model the property is set. The only diagnosis I can come up with is that until the window is actually visible the command is not bound to the command property of the button. I feel like may I'm missing a step somewhere or my implementation is not sound. below is some snipits of the code, please let me know if you need more.
Window constructor:
public PlottingViewModel ViewModel { get; set; }
public PlottingGUI()
{
InitializeComponent();
DataContext = (ViewModel = new PlottingViewModel());
_setDefaultSelections();
}
IList<RadioButton> buttons;
Setting default selections:
private void _setDefaultSelections()
{
buttons = new List<RadioButton>();
_getRadioButtons(this);
foreach (var setting in ViewModel.Settings.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
var settingValue = setting.GetValue(ViewModel.Settings);
var button = buttons.FirstOrDefault(btn => btn.Content.Equals(settingValue)
|| ((string)btn.CommandParameter).Equals(settingValue));
if (button == null)
continue;
button.IsChecked = true;
// NullReference here
// button.Command.Execute(button.CommandParameter);
}
}
one of the RadioButtons XAML:
<RadioButton Content="None"
Grid.Row="0"
Command="{Binding StampedCommand}"
CommandParameter="None"
Foreground="WhiteSmoke"/>
I feel, the only way i may be able to successfully complete this task is to execute the command directly from my viewmodel. (Which i don't want to do)
Thanks for reading..

To sum up comments at the point when you're calling _setDefaultSelections() bindings have not been updated yet, hence Command is still null, so you have to wait until everything is loaded. You can call _setDefaultSelections during Loaded event
Occurs when the element is laid out, rendered, and ready for interaction.

Related

WPF: How to have KeyBinding on Window that doesn't get swallowed by textbox

I want to rebind the "Up" key for my window to perform a command on my ViewModel.
My window contains 2 controls: ListView, TextBox.
If I do
<Window.InputBindings>
<KeyBinding Key="F5" Command={Binding SomeCommand} />
</Window.InputBindings>
Everything works correctly. However, if I set it to "Up", or certain other keys, the command does not get executed if the TextBox has focus. This tells me that the TextBox is handling these keys and swallowing the event that triggers the command. Is there any way to prevent that and to allow the window to get them similar to the Preview events (PreviewKeyUp) ?
If you want an application level shortcut, you can use InputManager
InputManager.Current.PreProcessInput += (sender,args)
{
var keyboardEvent = args?.StagingItem?.Input as KeyboardEventArgs;
if(keyboardEvent != null)
{
var keyArgs = keyboardEvent.RoutedEvent as KeyEventArgs;
if(keyArgs != null)
{
if(keyArgs.Key == Key.F5)
{
// Do something
}
}
}
};
I would recommend taking the logic above and incorporating it into a behavior that you can bind/map Keys to Commands.
Is there any way to prevent that and to allow the window to get them similar to the Preview events (PreviewKeyUp) ?
You could hook up an event handler to the KeyDownEvent using the overload of the AddHandler method that accepts a "handledEventsToo" boolean parameter and invoke your command from this event handler:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var vm = new ViewModel();
DataContext = vm;
AddHandler(KeyDownEvent, new KeyEventHandler((ss, ee) =>
{
vm.SomeCommand.Execute(null);
}), true);
}
}
There is no way to do this in pure XAML though but it doesn't really matter as far as the MVVM pattern is concerned as you simply move the invocation of the command from the XAML markup of the view to the code-behind class of the very same view. This doesn't break the MVVM pattern.

ContentPresenter not updating display correctly

In my program's main window I have a TreeView and a ContentPresenter. The display of the ContentPresenter is determined by what node is selected in the TreeView.
The name of one of my nodes is allowed to be changed by the user via contentMenu. All the user has to do is right click the node and select the new name out of the choices. The ContentPresenter is supposed to have a null display until the user chooses a name for the node.
The problem occurs when a new name is selected from the contentMenu. The ContentPresenter's display changes, like it should, but only after the user selects a different node (changing the display), and then re-selects the original node.
How do I make it so that the display on the ContentPresenter changes right when the TreeView node's name is changed?
TreeViewViewModel:
public class TreeViewViewModel : PropertyChangedBase
{
public TreeViewViewModel()
{
Node = new Node() { NodeName = "Blank", NodeDataModel = new NodeModel(),
Commands = { new Command(nodeType_name1), new Command(nodeType_name2) } };
}
//These functions call to the NodeName property in the TreeView's Data Model
private void nodeType_name1()
{
Node.NodeName = "Name1";
}
private void nodeType_name2()
{
Node.NodeName = "Name2";
}
}
XAML for MainWindow:
<!-- Tree view items & Functions -->
<TreeView Name="Tree_One" ItemsSource="{Binding DataTree.Data}" ... >
<TreeView.Resources>
<SolidColorBrush Color="LightSkyBlue" x:Key="{x:Static SystemColors.HighlightBrushKey}" />
</TreeView.Resources>
</TreeView>
<!--- Left Widget -->
<ContentPresenter Content="{Binding LeftWidget}" />
MainWindowViewModel:
public class MainWindowViewModel : PropertyChangedBase
{
private TreeViewViewModel _dataTree;
public MainWindowViewModel()
{
_dataTree = new TreeViewViewModel();
}
public TreeViewViewModel DataTree { ... }
//This function is in charge of changing the display of the ContentPresenter
// I think that my problem can probably be solved by doing something here
public void ChangeViews()
{
if (_dataTree.SelectedItem is Node)
{
var _node = _dataTree.SelectedItem as Node;
var nodeViewModel = new NodeViewModel(_node.NodeDataModel);
if (_node.NodeName== "Unknown")
LeftWidget = null; //This is the Content Presenter **
if (_node.NodeName == "Name1")
{
LeftWidget = nodeViewModel;
}
if (_node.NodeName == "Name2") {...}
}
}
}
Duh, thats a alot of code and its pretty difficult to understand what you up to since you seem to have controls in your ViewModel.
Or at least it looks to me that you have them in ViewModel. That is not very MVVM-alike my friend. :)
"The problem occurs when a new name is selected from the contentMenu. The ContentPresenter's display changes, like it should, but only after the user selects a different node (changing the display), and then re-selects the original node."
The property changed is not being fired because the new selected value is equal to the old one.
Pretty obvious, right?... no property was actually changed
But why do you want the ContentPresenter to update itself with the value that it already has?
You said when you select a node the ContentPresenter displays it properly and when you re-select the same the ContentPresenter is not doing anything.
Its not doing anything because it think it doesnt need to. Which is true.
So the question is why would you make ContentPresenter force to refresh on each value no matter if old value is the same as new one?
Though if you want to hack/trick a little bit, you can always set ContentPresenter's Content to null before you assign another value. :)
However, post us more code and we will be able to provide you a better solution to your issue.
I was able to fix this issue by calling ChangeViews(); in my MainWindowViewModel from my TreeViewViewModel. I did this by using a delegate property in the TVVM, and adding it to my MWVM. By doing this, the display is updated whenever ChangeViews(); is called.
This is the answer that I used.

How to force validation errors update on View from ViewModel using IDataErrorInfo?

I have a MVVM-based Window with many controls, and my Model implements IDataErrorInfo.
There is also a SaveCommand button, which performs validation by analysing Model.Error property.
The view displays the default red border around controls with errors only when I change the value of a particular control, or when I notify about the change of that property using PropertyChanged.
How can I force View to display all Validation errors even when I didn't touch the controls?
All my validation bindings include ValidatesOnDataErrors=True, NotifyOnValidationError=True.
I know one solution is to have an aggregate box with all the errors, but I would prefer to display errors on per-control basis.
I don't want to trigger Model.NotifyPropertyChanged for each bound property from ViewModel.
I use WPF 4.0, not Silverlight, so INotifyDataErrorInfo won't work.
You mention that you don't want to raise property changed for the properties you bind to, but that's really the simplest way to accomplish this. Calling PropertyChanged with no parameter will raise for all properties in your viewmodel.
Alternatively you can update the bindings (and force revalidation) on any control like this:
myControl.GetBindingExpression(ControlType.ControlProperty).UpdateSource();
The best solution I've found so far that works is to change DataContext to null and back to the instance of ViewModel.
This triggers the update for controls on the view that has DataContext bound to InnerViewModel:
public void ForceUpdateErrors() {
var tmpInnerVM = _mainViewModel.InnerViewModel;
_mainViewModel.InnerViewModel = null;
_mainViewModel.InnerViewModel = tmpInnerVM;
}
It's recommended to check if no data is lost after this trick. I had a case that this code triggered source update for ComboBox.SelectedItem with null but I managed to solve it. It was caused by using a resource-based BindingProxy and the order of DataContext=null propagation across control hierarchy.
This 'Hack' worked for me temporarily, to force the InotifyChanged event, just assign that control back it's own content. Do this before evaluating the HasError function of bindings. For example a textbox would be:
((TextBox)child).Text = ((TextBox)child).Text;
And then a complete example(before I hear this is not true MVVM, I directly got a handle on the grid for ease of showing this code snipet)
public bool Validate()
{
bool hasErr = false;
for (int i = 0; i != VisualTreeHelper.GetChildrenCount(grd); ++i)
{
DependencyObject child = VisualTreeHelper.GetChild(grd, i);
if (child is TextBox)
{
bool pp = BindingOperations.IsDataBound(child, TextBox.TextProperty);
if (pp)
{
((TextBox)child).Text = ((TextBox)child).Text;
hasErr = BindingOperations.GetBindingExpression(child, TextBox.TextProperty).HasError;
System.Collections.ObjectModel.ReadOnlyCollection<ValidationError> errors = BindingOperations.GetBindingExpression(child, TextBox.TextProperty).ValidationErrors;
if (hasErr)
{
main.BottomText.Foreground = Brushes.Red;
main.BottomText.Text = BindingOperations.GetBinding(child, TextBox.TextProperty).Path.Path.Replace('.', ' ') + ": " + errors[0].ErrorContent.ToString();
return false;
}
}
}
if (child is DatePicker)
{
...
}
}
return true;
}

Command not triggering on button click

In my SL4 app, I'm trying to trigger a Command from a button. The Command code is standard stuff that I have used without issue elsewhere, but I cannot get the Command to be called when I click the button.
This was driving me mental, so I eventually created a test page that had nothing on it but a button. The data context of the page is set to my ViewModel, and the ViewModel has an ICommand property on it. The DataContext is working as I can bind a textbox to a string property in the ViewModel. It's so basic, I can include all the relevant stuff here:
From the XAML:
xmlns:models="clr-namespace:x3.ViewModels"
...
<UserControl.DataContext>
<models:TestViewModel/>
</UserControl.DataContext>
<Button x:Name="TestButton" Command="{Binding TestCommand}" Content="AAAAGHH" />
From the ViewModel:
public class TestViewModel:INotifyPropertyChanged
{
ICommand _testCommand;
public ICommand TestCommand
{
get
{
_testCommand = new DelegateCommand(
commandParameter =>
{
var testButton = commandParameter as Button;
},
(commandParameter) => {return true;});
return _testCommand;
}
}
}
The DelegateCommand is part of Telerik.Windows.Controls. If I put a breakpoint at
_testCommand = new DelegateCommand
it gets hit when the page loads, but after that, I can click the button until my mouse wears out, and the command is never called.
For the sake of my mental health, I'd appreciate any help on offer.
Thanks
Mick
The get accessor for TestCommand is only called once - when the binding engine binds the Command of the Button to the TestCommand property. Putting your breakpoint on the first line of the get, it should be expected that it only gets hit once.
What you need to do is put your breakpoint on the code that executes when your command gets run. In your original example, this means break inside the delegate - i.e. on return true.
Edit: you can force the debugger to break in code as well using System.Diagnostics.Debugger.Break():
ICommand _testCommand;
public ICommand TestCommand
{
get
{
_testCommand = new DelegateCommand(
commandParameter =>
{
var testButton = commandParameter as Button;
},
(commandParameter) =>
{
System.Diagnostics.Debugger.Break(); // Force debugger to break
return true;
}
);
return _testCommand;
}
}
In order to isolate the problem, try using your own simple ICommand implementation, instead of Telerik's DelegateCommand.
If the problem still happens, you'll know it's somewhere around your XAML / data binding, and then I'd suggest you post more complete parts of your code so that someone here may help you.
If the problem disappears, you'll know it's something in Telerik DelegateCommand that decides not to call your lambda.
If you can post a more complete reproduction of the problem, it may also help. Because you posted only a minimal part of your code, and maybe the cause of the problem is missing from here...

wpf combobox selecteditem to null after onsourceinitialized

I have overrided the method OnSourceInitialized and I have one problem. After populating my combobox with source property from c# code I want automatically an item will appear selected in the combobox when a page is loaded (default value) but for some reason after onsourceinitialized method, the combobox selected item change to null.
EDIT
First of all, very good explanation thanks.
I'll try to explain more and I post some code following. I have made some modifications but without success. It continues not working.
My goal is to show a default value selected in the combobox when window is loaded and it is shown.
Initially, when user selects a option in menu application I do the following:
WinMain.xaml.cs:
namespace MyNamespace
{
public partial class WinMain : Window
{
<...>
private void mnuItemPreferences_Click(object sender, RoutedEventArgs e)
{
MyNamespace.Windows.EditPreferences editPrefWnd =
new MyNamesapece.Windows.EditPreferences();
//
// Modal window that I want to open with default values in comboboxes
//
editPrefWnd.ShowDialog();
}
<...>
} // end WinMain class
} // end namespace
EditPreferences.xaml.cs:
namespace MyNamespace.Windows
{
public partial class EditPreferences : Window
{
<...>
// My constructor
public EditPreferences()
{
//
// Handlers
//
Loaded += PreferencesWindow_Loaded;
Closing += PreferencesWindow_Closing;
InitializeComponent();
if (System.Environment.OSVersion.Version.Major < 6)
{
this.AllowsTransparency = true;
_bolAeroGlassEnabled = false;
}
else
{
_bolAeroGlassEnabled = true;
}
this.ShowInTaskbar = false;
} // end constructor
private void PreferencesWindow_Loaded(object sender,
System.Windows.RoutedEventArgs e)
{
if (this.ResizeMode != System.Windows.ResizeMode.NoResize)
{
//this work around is necessary when glass is enabled and the
//window style is None which removes the chrome because the
//resize mode MUST be set to CanResize or else glass won't display
this.MinHeight = this.ActualHeight;
this.MaxHeight = this.ActualHeight;
this.MinWidth = this.ActualWidth;
this.MaxWidth = this.ActualWidth;
}
//
// Populate comboboxes
//
cbLimHorasExtra.ItemsSource = Accessor.GetLimHorasExtraSorted();
cbFracHorasExtra.ItemsSource = Accessor.GetFracHorasExtraSorted();
//
// Fill controls with default values (see below)
//
FillControls();
//
// Install other handlers
//
rdoBtnOTE.Checked += this.rdoBtnOTE_Checked;
rdoBtnOTM.Checked += this.rdoBtnOTM_Checked;
chkboxRestrict.Checked += this.chkboxRestrict_Checked;
expAdditionalDetails.Collapsed +=
this.expAdditionalDetails_Collapsed;
expAdditionalDetails.Expanded += this.expAdditionalDetails_Expanded;
cbLimHorasExtra.SelectionChanged +=
this.cbLimHorasExtra_SelectionChanged;
cbFracHorasExtra.SelectionChanged +=
this.cbFracHorasExtra_SelectionChanged;
}
protected override void OnSourceInitialized(System.EventArgs e)
{
base.OnSourceInitialized(e);
if (_bolAeroGlassEnabled == false)
{
//no aero glass
this.borderCustomDialog.Background =
System.Windows.SystemColors.ActiveCaptionBrush;
this.tbCaption.Foreground =
System.Windows.SystemColors.ActiveCaptionTextBrush;
this.borderCustomDialog.CornerRadius =
new CornerRadius(10, 10, 0, 0);
this.borderCustomDialog.Padding =
new Thickness(4, 0, 4, 4);
this.borderCustomDialog.BorderThickness =
new Thickness(0, 0, 1, 1);
this.borderCustomDialog.BorderBrush =
System.Windows.Media.Brushes.Black;
}
else
{
//aero glass
if (VistaAeroAPI.ExtendGlassFrame(this,
new Thickness(0, 25, 0, 0)) == false)
{
//aero didn't work make window without glass
this.borderCustomDialog.Background =
System.Windows.SystemColors.ActiveCaptionBrush;
this.tbCaption.Foreground =
System.Windows.SystemColors.ActiveCaptionTextBrush;
this.borderCustomDialog.Padding =
new Thickness(4, 0, 4, 4);
this.borderCustomDialog.BorderThickness =
new Thickness(0, 0, 1, 1);
this.borderCustomDialog.BorderBrush =
System.Windows.Media.Brushes.Black;
_bolAeroGlassEnabled = false;
}
}
}
private void FillControls()
{
tblPreferencias tbl_pref = null;
//
// Obtain data (a record with fields)
// Accessor is a class where I define the methods to
// obtain data of different tables in my database
//
tbl_pref = Accessor.GetActualPreferencias();
//
// Only returns one register
//
if (tbl_pref != null)
{
rdoBtnOTE.IsChecked = (bool)tbl_pref.OTE;
rdoBtnOTM.IsChecked = (bool)tbl_pref.OTM;
chkboxRestrict.IsChecked =
(bool)tbl_pref.RestriccionHExtraTipoA;
// Here the value assigned is always in the range of the values
// which combo has been populated.
// With one 0 ... 8
// I debbugged it and works.
// selected value (no null) and text gets the correct value I
// want but after OnSourceInitialized method is executed I note
// that for some rease selected value property gets value null
cbLimHorasExtra.Text = tbl_pref.LimiteHorasExtra.ToString();
cbFracHorasExtra.Text =
tbl_pref.FraccionDeMinutosExtra.ToString();
}
}
<...>
} // end EditPreferences class
} // end namespace
EditPreferences.xaml (I put as example one of the comboboxes):
<Window x:Class="MyNamespace.Windows.EditPreferences"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="EditPreferences" Height="Auto" Width="500"
Background="{x:Null}"
SnapsToDevicePixels="True" SizeToContent="Height"
WindowStartupLocation="CenterScreen"
ResizeMode="NoResize"
WindowStyle="None"
Margin="0,0,0,0"
>
<...>
<ComboBox x:Name="cbLimHorasExtra"
DisplayMemberPath="LimHora"
SelectedValuePath="Id"
SelectedItem="{Binding Path=Id}"
VerticalAlignment="Center"
HorizontalContentAlignment="Right"
Width="50"/>
<...>
</Window>
Accessor.cs:
namespace GesHoras.Classes
{
class Accessor
{
<...>
// This method is used to populate the combobox with its values
// tblLimHorasExtra is a table in my SQL Database
// Its fields are:
//
// Id : int no null (numbers 1 ... 9)
// LimHora: int no null (numbers 0 ... 8)
//
public static System.Collections.IEnumerable GetLimHorasExtraSorted()
{
DataClassesBBDDDataContext dc = new
DataClassesBBDDDataContext();
return (from l in dc.GetTable<tblLimHorasExtra>()
orderby l.LimHora
select new { Id=l.Id, LimHora=l.LimHora });
}
// tblPreferencias is a table in my SQL Database
// Its fields are:
//
// Id : int no null
// Descripcion : varchar(50) no null
// OTE : bit no null
// OTM : bit no null
// LimiteHorasExtra : int no null
// FraccionDeMinutosExtra : int no null
// RestriccionHExtraTipoA : bit no null
//
public static tblPreferencias GetActualPreferencias()
{
DataClassesBBDDDataContext dc = new
DataClassesBBDDDataContext();
return (from actP in dc.GetTable<tblPreferencias>()
where (actP.Id == 3)
select actP).SingleOrDefault<tblPreferencias>();
}
<...>
} // end class
} // end namespace
The problem I see is that when method fillControls is executed all is ok, selectedvalue and text property for the combobox is correct (I have debbugged it and is correct) but after executing OnSourceInitialized method, selectedvalue property for the combobox gets null value.
Also I note that, when window opens, the comboboxes appear with the default values selected that I want but quickly I see that for some reason their values selected turns to empty in the comboboxes. It's like some event (I think after executing OnSourceMethod because I have debugged and see how it change to null) makes the selected default values that appears ok in the comboboxes turn to empty.
I have tested that comboboxes are populated correctly because once the window is shown I click in the comboboxes and I can see they are populated ok.
EDIT 2
Also I have forced selected index for combobox in fillControls method by doing:
cbLimHorasExtra.SelectedIndex = 1;
but without success...
The combobox is populated with values: 0 to 8 both included.
Cause
This appears to be the problem:
SelectedItem="{Binding Path=Id}"
If the "Id" property in the DataContext is not an item in the ItemsSource, SelectedItem will be set to null.
Timing
When InitializeComponent is called, it parses the XAML which sets the SelectedItem binding. If the DataContext is not set, then initially this will be null. Later when DataContext is set, the binding is re-evaluated. If Id is in the list at that point, the SelectedItem is set. Otherwise it is set to null.
Any binding that cannot be evaluated initially during InitializeComponent is scheduled using the dispatcher to be re-evaluated once all events have fired. Without details on how your DataContext is being set I can't give specifics, but my guess is that one of your binding is getting deferred so your {Binding Path=Id} binding is evaluated in a dispatcher callback.
A dispatcher callback is not an event - it is a prioritized work queue. If you have this kind of situations your choices are:
Change the bindings so they can be evaluated during initialization
Use a Dispather.BeginInvoke to schedule your own callback to execute after the Binding completes
Let the Binding take care of setting the SelectedItem rather than setting manually in code
Additional notes
Your use of SelectedValueSource looks suspicious. Your binding to SelectedItem seems to indicate that each item in the ItemsSource is an "Id", but your definition of SelectedValueSource seems to indicate that each item in the ItemsSource contains an "Id". It is rare to find a data structure where the structure itself is called "Id" by another structure, yet it itself has an "Id" field. Thus I suspect some confusion here. Without seeing your actual data structures I can't say more.
Your use of OnSourceInitialized also makes it appear you have a misunderstanding. The "Source" in the name of OnSourceInitialized refers to a "presentation source" such as a Win32 hWnd, not a source of data. The purpose of OnSourceInitialized is to interact at a low level with the Windows operating system, or to update your application based on where it is being presented. Your use seems completely unrelated to this. I would recommend you stay away from OnSourceInitialized. Generally the best time to initialize ComboBoxes and such is to just provide it in your view model and let data binding take care of it. As soon as the view model is available the data will be populated with no code required.
Set the SelectedIndex property at the end of your override, by the way, i can't seem to find OnSourceInitialised, only Initialised. But it should still work if you set it at the end of your code.
private void MyListBox_Initialized(object sender, EventArgs e)
{
// Run some code
if (MyListBox.Items.Count > 0)
{
MyListBox.SelectedIndex = 0;
}
}
I don't have a real answer to your question, but OnSourceInitialized seems to be too early in the initialization process.
Again, I have not tried your exact scenario, but many problems like this one are solved by calling FillControls (i.e. setting the selected item) in the Loaded event instead of earlier.
I have solved it!
The problem was in binding the SelectedItem property in EditPreferences.xaml:
<ComboBox x:Name="cbLimHorasExtra"
DisplayMemberPath="LimHora"
SelectedValuePath="Id"
SelectedItem="{Binding Path=Id}"
VerticalAlignment="Center"
HorizontalContentAlignment="Right"
Width="50"/>
The solution is to change to:
<ComboBox x:Name="cbLimHorasExtra"
DisplayMemberPath="LimHora"
SelectedValuePath="Id"
SelectedItem="Id"
VerticalAlignment="Center"
HorizontalContentAlignment="Right"
Width="50"/>

Categories

Resources