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"/>
Related
I have a form that has a dynamic amount of datagrids that are brought in programmatically each one on a new tabpage.
My problem is that I need to change the Header of each column. I have tried doing it through a method
DataGridForSupplier.Columns[0].Header = "123";
but that keeps crashing with an error:
Index was out of range. Must be non-negative and less than the size of the collection
Turns out the problem is that the grid wasn't finished loading. So after waiting for all tabpage to load and add data to all the grids , even then the code
DataGridForSupplier.Columns[0].Header = "123";
would still crash. If the tabs are left to load on their own with no header tampering then the datagrid shows fine.
I would just LOVE to do this in XAML problem is that seeing that I don't know how many grids will load at run time I tried doing this at the back. So I'm open to any solution at this point. I tried finding a solution that would incorporate something that would 'theme' all the datagrids. Luckily all the datagrids headers will repeat across all tabs. So header 1 on tabpage 1 - 10 will be the same. Header 2 on tabpage 1 - 10 will be the same
Something like
<DataGridTemplateColumn.Header>
<TextBlock Text="{Binding DataContext.HeaderNameText, RelativeSource=>> RelativeSource AncestorType={x:Type DataGrid}}}" />
</DataGridTemplateColumn.Header>
but this needs to repeat for every Grid. This seems to escape me at the moment.
Any help would be welcome.
A rather lengthy answer, but this solution does not require any additional libraries, 3rd party tools, etc. You can expand it as you want later such as for adding hooks to mouse-move/over/drag/drop/focus, etc. First the premise on subclassing which I found out early in my learning WPF. You can not subclass a xaml file, but can by a .cs code file. In this case, I subclassed the DataGrid to MyDataGrid. Next, I created an interface for a known control type to ensure contact of given functions/methods/properties. I have stripped this version down to cover just what you need to get.
The interface below is just to expose any class using this interface MUST HAVE A METHOD called MyDataGridItemsChanged and expects a parameter of MyDataGrid.. easy enough
public interface IMyDataGridSource
{
void MyDataGridItemsChanged(MyDataGrid mdg);
}
Now, declaring in-code a MyDataGrid derived from DataGrid. In this class, I am adding a private property of type IMyDataGridSource to grab at run-time after datagrids are built and bound.
public class MyDataGrid : DataGrid
{
// place-holder to keep if so needed to expand later
IMyDataGridSource boundToObject;
public MyDataGrid()
{
// Force this class to trigger itself after the control is completely loaded,
// bound to whatever control and is ready to go
Loaded += MyDataGrid_Loaded;
}
private void MyDataGrid_Loaded(object sender, RoutedEventArgs e)
{
// when the datacontext binding is assigned or updated, see if it is based on
// the IMyDataGridSource object. If so, try to type-cast it and save into the private property
// in case you want to add other hooks to it directly, such as mouseClick, grid row changed, etc...
boundToObject = DataContext as IMyDataGridSource;
}
// OVERRIDE the DataGrid base class when items changed and the ItemsSource
// list/binding has been updated with a new set of records
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
// do whatever default behavior
base.OnItemsChanged(e);
// if the list is NOT bound to the data context of the IMyDataGridSource, get out
if (boundToObject == null)
return;
// the bound data context IS of expected type... call method to rebuild column headers
// since the "boundToObject" is known to be of IMyDataGridSource,
// we KNOW it has the method... Call it and pass this (MyDataGrid) to it
boundToObject.MyDataGridItemsChanged(this);
}
}
Next into your form where you put the data grid. You will need to add an "xmlns" reference to your project so you can add a "MyDataGrid" instead of just "DataGrid". In my case, my application is called "StackHelp" as this is where I do a variety of tests from other answers offered. The "xmlns:myApp" is just making an ALIAS "myApp" to the designer to it has access to the classes within my application. Then, I can add
<Window x:Class="StackHelp.MyMainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:myApp="clr-namespace:StackHelp"
Title="Main Window" Height="700" Width="900">
<StackPanel>
<!-- adding button to the main window to show forced updated list only -->
<Button Content="Refresh Data" Width="100"
HorizontalAlignment="Left" Click="Button_Click" />
<myApp:MyDataGrid
ItemsSource="{Binding ItemsCollection, NotifyOnSourceUpdated=True}"
AutoGenerateColumns="True" />
</StackPanel>
</Window>
Now, into the MyMainWindow.cs code-behind
namespace StackHelp
{
public partial class MyMainWindow : Window
{
// you would have your own view model that all bindings really go to
MyViewModel VM;
public MyMainWindow()
{
// Create instance of the view model and set the window binding
// to this public object's DataContext
VM = new MyViewModel();
DataContext = VM;
// Now, draw the window and controls
InitializeComponent();
}
// for the form button, just to force a refresh of the data.
// you would obviously have your own method of querying data and refreshing.
// I am not obviously doing that, but you have your own way to do it.
private void Button_Click(object sender, RoutedEventArgs e)
{
// call my viewmodel object to refresh the data from whatever
// data origin .. sql, text, import, whatever
VM.Button_Refresh();
}
}
}
Finally to my sample ViewModel which incorporates the IMyDataGridSource
public class MyViewModel : IMyDataGridSource, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
public ObservableCollection<OneItem> ItemsCollection { get; set; }
= new ObservableCollection<OneItem>();
public void Button_Refresh()
{
ItemsCollection = new ObservableCollection<OneItem>
{
new OneItem{ DayName = "Sunday", DayOfWeek = 0},
new OneItem{ DayName = "Monday", DayOfWeek = 1},
new OneItem{ DayName = "Tuesday", DayOfWeek = 2},
new OneItem{ DayName = "Wednesday", DayOfWeek = 3},
new OneItem{ DayName = "Thursday", DayOfWeek = 4},
new OneItem{ DayName = "Friday", DayOfWeek = 5 },
new OneItem{ DayName = "Saturday", DayOfWeek = 6 }
};
RaisePropertyChanged("ItemsCollection");
}
// THIS is the magic hook exposed that will allow you to rebuild your
// grid column headers
public void MyDataGridItemsChanged(MyDataGrid mdg)
{
// if null or no column count, get out.
// column count will get set to zero if no previously set grid
// OR when the items grid is cleared out. don't crash if no columns
if (mdg == null)
return;
mdg.Columns[0].Header = "123";
}
}
Now, taking this a step further. I don't know how you manage your view models and you may have multiple grids in your forms and such. You could create the above MyViewModel class as a smaller subset such as MyDataGridManager class. So each datagrid is bound to its own MyDataGridManager instance. It has its own querying / populating list for the grid, handling its own rebuild column headers, mouse clicks (if you wanted to expand), record change selected, etc.
Hope this helps you some. Again, this does not require any other 3rd party libraries and you can extend as you need. I have personally done this and more to the data grid and several other controls for certain specific pattern handling.
I've created an example to illustrate my problem.
ViewModel:
public class VM : INotifyPropertyChanged
{
private double _value = 1;
public double Value
{
get { return _value; }
set
{
_value = value;
OnPropertyChanged();
}
}
public VM()
{
var timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromTicks(1);
timer.Tick += (s, e) => { Value += 1; };
timer.Start();
}
// OnPropertyChanged stuff ...
}
}
View:
<Window.DataContext>
<namespace:VM/>
</Window.DataContext>
<Grid>
<TextBox Text="{Binding Value, IsAsync=True, FallbackValue=Test}"/>
</Grid>
When running my application the text in the textbox flickers. During the update process the FallbackValue is displayed, which makes no sense to me.
Does anyone knows the purposes or what are the benefits that during the update process the FallbackValue is displayed? Is there a way to display the old Value during an async update process?
This seems normal to me, given that you are using IsAsync=True in your binding. From the documentation:
While waiting for the value to arrive, the binding reports the FallbackValue, if one is available
When the PropertyChanged event is raised, WPF initiates the process of updating the target of the binding. Normally this would happen synchronously, with the property getter called immediately to update the value.
But you are using IsAysnc=True, so instead WPF fills in the target with the fallback value, and starts an asynchronous request to retrieve the actual property value later. Until that request has completed, the fallback value is displayed.
Does anyone knows the purposes or what are the benefits that during the update process the FallbackValue is displayed?
Per the documentation, the intent behind the IsAsync=True setting is that it's used when the property getter is, or could be, slow. Your code has told WPF that the property value has changed, so it knows the old value is no longer valid. Your code has also told (via the IsAsync in the XAML) that the property getter could take some time to provide the new value, so it defers retrieving that value until later.
In the meantime, what should WPF display? That's what the fallback value is there for.
Is there a way to display the old Value during an async update process?
If you don't want the behavior that is designed for this feature in WPF, you should just retrieve the new data asynchronously yourself, and update the property via the setter when you have it. It's not a good idea for a property getter to be slow anyway, so this would be a better design in any case.
I had the same problem but with an image source. I've removed the IsAsync on the binding and I have made my getter async:
// This field hold a copy of the thumbnail.
BitmapImage thumbnail;
// Boolean to avoid loading the image multiple times.
bool loadThumbnailInProgress;
// I'm using object as the type of the property in order to be able to return
// UnsetValue.
public object Thumbnail
{
get {
if (thumbnail != null) return thumbnail;
if (!loadThumbnailInProgress) {
// Using BeginInvoke() allow to load the image asynchronously.
dispatcher.BeginInvoke(new Action(() => {
thumbnail = LoadThumbnail();
RaisePropertyChanged(nameof(Thumbnail));
}));
loadThumbnailInProgress = true;
}
// Returning UnsetValue tells WPF to use the fallback value.
return DependencyProperty.UnsetValue;
}
}
Sometimes a binding will fail, failure is important to consider. Fallback value option presents users a message if an error occurs, rather than nothing happening. If you would like your fallbackvalue to display the previous value contained, I could think of a few ways of trying : possibly saving the value in a reference string and/or to another control, then binding to that control
But if you don't want the fallbackvalue displayed at all, you need to do a code inspection to see how your binding is failing/or is slow, and contain it in your code behind
I've found an approach to avoid flickering by just inheriting from textbox and overriding it's textproperty-metadata.
Custom TextBoxControl
public class CustomTextBox : TextBox
{
static CustomTextBox()
{
TextProperty.OverrideMetadata(typeof(CustomTextBox), new FrameworkPropertyMetadata(null, null, CoerceChanged));
}
private static object CoerceChanged(DependencyObject d, object basevalue)
{
var tb = d as TextBox;
if (basevalue == null)
{
return tb.Text;
}
return basevalue;
}
}
View
<Window.DataContext>
<namespace:VM/>
</Window.DataContext>
<Grid>
<namespace:CustomTextBox Text="{Binding Value, IsAsync=True}"/>
</Grid>
It's important to have the text-binding without a fallbackvalue. So during update process the text is set to the textproperty defalut value - so in this case to null.
The CoerceChanged handler checks whether the new value is null. If it's so he returns the old value so that during update process there is still the old value displayed.
At first I will post the code, it is short and quite clear.
cb_currentProfile is a ComboBox filled with 3 items when form is loaded:
delegate void SetCurrentProfileCallback(int index);
private void SetCurrentProfile(int index) // Set index of Combobox.SelectedItem
{
if (this.cb_currentProfile.InvokeRequired)
{
SetCurrentProfileCallback d = new SetCurrentProfileCallback(SetCurrentProfile);
this.Invoke(d, new object[] { index });
}
else
{
this.cb_currentProfile.SelectedItem = 2; // Won't work
this.cb_currentProfile.Visible = false; // It works
}
}
The problem is that when I try to change SelectedItem property, then it won't do nothing (no crash, just nothing happens).
I am sure that this code is reached in my form application.
At now I am making it in .NET 4.6 (but it was not working in v4.5 either)
The place where I am calling this method is in Task body:
Task.Run(() =>
{
while(true)
{
// ...
SetCurrentProfile(2);
// ...
Thread.Sleep(100);
}
});
I think that the problem is related to DataSource that seems to be invisible by other thread than main UI's.
I am also sure that data are loaded to ComboBox before code reaches a Task creation.
Edit 1 - selected item is null, Count property returns 0
When I used a debugger to check for some data, the results are:
var x = this.cb_currentProfile.SelectedItem; // null
var y = this.cb_currentProfile.Items.Count; // 0
It looks like, with the this.cb_currentProfile.SelectedItem = 2 statement, you intend to set the selection of the ComboBox by index. The ComboBox.SelectedItem Property accepts an Object and attempts to find it in its collection of items, selecting it if successful, and doing nothing otherwise. To select a particular index of the ComboBox, set the ComboBox.SelectedIndex Property instead.
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.
Next code works as expected:
AskWindow.xaml:
<Window
x:Class='AskWPF.AskWindow'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
>
<DataGrid ItemsSource="{Binding SimpleItems}" />
</Window>
AskWindow.xaml.cs:
namespace AskWPF {
public class SimpleRow {
private string firstColumn;
private string secondColumn;
public SimpleRow(string first, string second) {
firstColumn = first;
secondColumn = second;
}
public string FirstColumn {
get { return firstColumn; }
set { firstColumn = value; }
}
public string SecondColumn {
get { return secondColumn; }
set { secondColumn = value; }
}
}
public partial class AskWindow : Window {
private ObservableCollection<SimpleRow> simpleItems;
public AskWindow() {
InitializeComponent();
DataContext = this;
simpleItems = new ObservableCollection<SimpleRow>();
simpleItems.Add(new SimpleRow("row 0, column 0", "row 0, column 1"));
simpleItems.Add(new SimpleRow("row 1, column 0", "row 1, column 1"));
}
public ObservableCollection<SimpleRow> SimpleItems {
get { return simpleItems; }
}
}
}
But if set DataContext='{Binding RelativeSource={RelativeSource Self}}' in Window tag and comment line DataContext=this we get an empty window. Why?
AskWindow.xaml:
<Window .... DataContext='{Binding RelativeSource={RelativeSource Self}}'>
<DataGrid ItemsSource="{Binding SimpleItems}" />
</Window>
AskWindow.xaml.cs:
...
public AskWindow() {
InitializeComponent();
// DataContext = this;
simpleItems = new ObservableCollection<SimpleRow>();
simpleItems.Add(new SimpleRow("row 0, column 0", "row 0, column 1"));
simpleItems.Add(new SimpleRow("row 1, column 0", "row 1, column 1"));
}
...
Here is my guess. In both cases at one point your Collection is null. To be precise right after InitializeComponent. At this point the initial databinding got the data, but no datacontext. Now by setting the DataContext your property gets raised and every binding related to it, gets invalidated and refreshed. Here is my guessing part, the reason it works is that the binding to the ItemsSource is deferred therefore it works to just set the collection in the next line.
So in short: Setting the Datacontext will retrigger the binding. But in your RelativeSource example your binding worked from the beginning but the collection was null and you never told wpf to refetch the binding.
If you directly initialize your collection it should work fine.
I suspect it has to do with how and when certain kinds of bindings are evaluated. In the latter case i think that the binding may retrieve the value of the collection property while it still is null, then you change the property (by setting the field) without firing any change notification for the affected property.
Would recommend to move the InitializeComponent call to the end of the constructor or to at least set the field beforehand.
Usually i use a readonly field and just initialize it right away:
private readonly ObservableCollection<Data> collection =
new ObservableCollection<Data>();
public ObservableCollection<Data> Collection { get { return collection ; } }
Actually the binding is correct and it works also. In order the screen to be updated the binding has to receive notifications that something changed. A binding first evaluates and then listens for notifications. In your second version the binding first evaluates when InitializeComponent is run, but there are no values at that moment so you see nothing. After that the values are created but the binding does not reevaluate because no notifications are sent.
So yes one solutuion would be to initialize the collection prior to InitializeComponent.
...
private ObservableCollection<SimpleRow> simpleItems = new ObservableCollection<SimpleRow>();
...
Another solution would be stupid and an overkill to notify the binding that something was changed.
Just a note, probably this is for learning purposes because the UI should not be mixed up with the model.