WPF MVVM DataGrid Binding - c#

Well, not the problem, just working on my course work and trying an MVVM pattern. All is working well or I think so. But this thing shouldn't work and I don't understand why is it okay...
Here is a ViewModel:
public class ViewModel : OnPropertyChangedClass
{
private readonly static IService service = new Service();
private static reFolder selectedFolder;
private ObservableCollection<reObject> folders;
public ObservableCollection<reObject> Folders
{
get => folders;
set
{
folders = value;
OnPropertyChanged();
}
}
private reFolder folderExplorer;
public reFolder FolderExplorer
{
get => folderExplorer;
set
{
folderExplorer = value;
OnPropertyChanged();
}
}
//these methods are called from MainWindow.xaml.cs, it's the only way I've found to track nodes
public static void NodeExpanded()
{
service.UpdateFolder(selectedFolder);
service.ChangeFolderExpandedState(selectedFolder);
}
public static void NodeSelected(reFolder folder)
{
selectedFolder = folder;
service.UpdateExplorer(selectedFolder);
}
public ViewModel()
{
folders = service.UpdateDrives();
folderExplorer = service.UpdateExplorer(null);
}
}
And the part in MainWindow.xaml
<DataGrid x:Name="Explorer" ItemsSource="{Binding FolderExplorer.FoldersFiles}" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="{x:Null}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" FontWeight="Bold" FontSize="16" IsReadOnly="True" Width="168" Foreground="#FF1BB37C"/>
</DataGrid.Columns>
</DataGrid>
As I understand, DataGrid updates after calling OnPropertyChanged(), but it's only called once in the constructor:
folderExplorer = service.UpdateExplorer(null);
But when I just select something in the TreeView in runtime, xaml.cs calles NodeSelected method:
service.UpdateExplorer(selectedFolder);
It not triggers setter or OnPropertyChanged in debug, but DataGrid still updates.
Can somebody explain me this, please. If you need more code, just tell me, thanks.

Your question is a bit difficult to understand. I read it like you wonder about the different behavior of the DataGrid compared to the TreeView?
Your expectations are correct: the DataGrid will update the cell, if the bound property has changed.
But this is never happening in your case - at least this is what I can tell from your posted code. Opposed to your claims, even your posted constructor is not raising the PropertyChanged event:
// Assignment of a field. Fields never raise PropertyChanged (your code)
folderExplorer = service.UpdateExplorer(null);
// Assignment of a property. Properties can raise PropertyChanged
FolderExplorer = service.UpdateExplorer(null);
You didn't posted this code, because you thought it is unimportant. That's why I assume you are expecting the execution of the FolderExplorer set method.
But this is not happening for a good reason: the properties that are actually edited and updated are inside the FolderExplorer.FoldersFiles collection. Set a breakpoint into the class of the items that are contained in the FoldersFiles on the Name property and you will see, that the properties set methods are called whenever you edit the corresponding DataGrid cell.
If a nested property changes e.g. FolderExplorer.FoldersFiles/FolderFile.Name, then PropertyChanged is only raised on the actual (nested) property and not on all the top level properties along the path of a nested property call.
Furthermore your comparison of the DataGrid behavior with the TreeView behavior is not valid. Editing a cell modifies the data while selecting doesn't. So clicking on a cell won't raise any PropertyChanged events in the first place. In this scenario a property change would only occur, if you bind the DataGrid.SelectedItem to a property of your ViewModel.

Related

Binding textblock value doesn't change after changing binded value

Why does TextBlock "T1" not show "101" after clicking on Button "B1" and still shows "100"?
<StackPanel>
<TextBlock Name="T1" Text="{x:Bind value, Mode=OneWay}"/>
<Button Name="B1" Content="+1" Click="B1_Click"/>
</StackPanel>
and
public sealed partial class MainPage : Page
{
public int value;
public MainPage()
{
InitializeComponent();
value = 100; // initial value
}
private void B1_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
value = value + 1;
}
}
Your value is a field, yes it works with x:Bind. Actually it should even work being private.
But in order for the UI to update the value of value, you need to make one of the three changes below -
Call Bindings.Update() after setting it.
change it to a dependency property.
Change it to a normal property but implement INotifyPropertyChanged on your page and raise property changed event on the setter. You can read more from here.
But which one to pick? This is normally what I do -
If the property rarely changes, I use Bindings.Update() and remove Mode=OneWay from the binding to have the best performance.
If the property lives in the code-behind of a UI element (like in your case), I go with a dependency property.
If the property lives inside a ViewModel, I use INPC.

TreeViewItem not updating with bound ItemsSource, ObservableCollection, and INotifyPropertyChanged

I know this question has been asked a lot but even after trying all of the different answers I still can't get this to work for me. The object I'm trying to bind to is updating correctly in the code behind so the only thing that isn't working is the children of the TreeViewItem updating when the ItemsSource is changed.
It seems that I have everything set up correctly but maybe there is something about how I am tying things together that is making this not work. I am using C# .NET 4.5 WPF project in VS 2015 in Windows 7. I am binding to a static classes' static property that has only a get method to a TreeViewItem's ItemsSource and setting DisplayMemberPath.
XAML:
<!-- Menu tree -->
<TreeView Grid.Column="0"
x:Name="menutree"
Background="Transparent"
BorderThickness="0">
<!-- Profiles TVI -->
<TreeViewItem Header="{x:Static loc:Resources.profiles}"
IsExpanded="True">
<!-- Color profile TVI -->
<TreeViewItem x:Name="colorTvi"
Header="{x:Static loc:Resources.colorProfiles}"
MouseRightButtonDown="colorTvi_MouseRightButtonDown"
DisplayMemberPath="Name"
ItemsSource="{Binding Source={x:Static local:Shared.ColorProfiles}, Mode=OneWay}" />
<TreeViewItem ...
Class / Properties being bound to:
public static class Shared
{
#region Getter / Setter
// Notify property changed
public static NotifyChanged Notify { get; set; } = new NotifyChanged();
// All profiles that have been created
public static List<Profile> Profiles
{
get { return _Profiles; }
set
{
// Set profile
_Profiles = value;
Notify.OnPropertyChanged(nameof(Profiles));
Notify.OnPropertyChanged(nameof(ColorProfiles));
}
}
private static List<Profile> _Profiles = new List<Profile>();
// Color profiles
public static ObservableCollection<ColorProfile> ColorProfiles
{
get
{
return new ObservableCollection<ColorProfile>(
Profiles?.Where(m => m.GetType() == typeof(ColorProfile))?.Cast<ColorProfile>()?.ToList() ??
new List<ColorProfile>());
}
}
#endregion
}
The NotifyChanged class:
// Property changed class
public class NotifyChanged : INotifyPropertyChanged
{
// Property changed event
public event PropertyChangedEventHandler PropertyChanged;
// Notify property changed
public void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
I would like to get this to work without having to call Refresh() in code behind. I have tried binding to Shared.Profiles directly but that doesn't help any. ColorProfile is a base class that inherits from Profile.
Hopefully there's some stupid, simple thing I'm missing. Thanks in advance for the help.
UPDATE :
On further inspection it actually looks like the ItemsSource isn't even updating. During debugging I can see in the control's property explorer that the ItemsSource is bound to an ObservableCollection but the ItemsSource is not reflecting the changes made to the list. If I manually bind in the code behind then it works.
Notify is the one shouting PropertyChanged, on itself.
No one is binded to Notify so no one is updating.
The one who needs to implement INotifyPropertyChanged, or inherit from NotifyChanged is Shared.

WPF Datagrid Bindings Not Updating When ObservableCollection Values Updated

I have a WPF DataGrid bound to an ObservableCollection called "Personnel". I have a DataGridCheckBoxColumn in the DataGrid that is editable. The CheckBoxColumn is bound to a bool value called "AircraftCommanderSelected" in my collection. When a row is selected and the checkbox is checked, an event is fired to update the collection so that all AircraftCommanderSelected values for each "Personnel" are set to false (except for the one that was just set to true). With that said, my collection is updating correctly, but my datagrid will not 'uncheck' the previously checked boxes, who's bound value has been changed to false. How can I notify that the value has been changed? Below is my code (modified for easier reading).
Class
public class Personnel
{
///
///Other objects removed for reading purposes. All follow same format.
///
private bool aircraftCommanderSelected;
public bool AircrafCommanderSelected
{
get { return this.aircraftCommanderSelected; }
set
{
if(this.aircraftCommanderSelected != value)
{
this.aircraftCommanderSelected = value;
this.NotifyPropertyChanged("AircraftCommanderSelected");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(strin propName)
{
if(this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
XAML
<DataGrid Name="dataGrid" AutoGenerateColumns="False" SelectedItem="{Binding Personnel}" CanUserDeleteRows="False" CanUserAddRows="False" IsReadOnly="False" SelectionMode="Single" CellEditEnding="dataGrid_CellEditEnding">
<DataGrid.Columns>
<DataGridCheckBoxColumn local:DataGridUtil.Name="ac" Header="AC" Binding="{Binding AircraftCommanderSelected}"/>
</DataGrid.Columns>
</DataGrid>
Code Behind
private void dataGrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
foreach (Personnel p in vm.personnel)//Loop through each item in the collection
{
//per.ID is the ID of the person who was just edited.
//This sets everyones value to false except the person that was just edited.
if (p.ID != per.ID) { p.AircraftCommanderSelected = false; }
}
}
When the collection is modified and the property changed event is fired, shouldn't the datagrid update?
I have found a solution but it involves multithreading, which seems like an improper solution to this problem. I also don't like how it refreshes the whole grid and deselects my current selection
dataGrid.Dispatcher.BeginInvoke(new Action(() => dataGrid.Items.Refresh()), System.Windows.Threading.DispatcherPriority.Background);
Any help is appreciated.
Thanks
-Justin
There's a series of things you need to check:
Are the objects in your ObservableCollection implementing INotifyPropertyChanged properly? (Not in the code it posted)
Are there any misspellings or capitalization changes with the property name? If you are on .NET 4.6 you can use the new nameof() property to make sure of that.
If the value can be changed from code-behind, then you must use Mode=TwoWay
By default most bindings are OneWay. If your binding mode is one way and you change the value in code behind, the binding will break and no longer work until you reload the XAML.
Even though you Personnel class has a PropertyChanged event, its declaration does not indicate it implements INotifyPropertyChanged. Change the class declaration so it reads:
public class Personnel : INotifyPropertyChange {
// rest of code here
}
Also, the binding needs to be two way. I don't know what the default is for a DataGridCheckBoxColumn, but it can't hurt to make it explicit.
Update your binding with BindingMode.TwoWay
<DataGridCheckBoxColumn local:DataGridUtil.Name="ac" Header="AC" Binding="{Binding AircraftCommanderSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
because you are trying to change value from code.

Binding the TextBlock of a Listview's GridView's DataGridCell

I've got an ObservableCollection of model objects that I'm attempting to display in the DataGridCells of a GridView in a ListView. For this simplified example, let's say that my model objects all have "MyString," and that I'm attempting to show "MyString" in a TextBlock inside of each row's DataGridCell.
As the ObservableCollection adds or removes these model objects, the ListView shows the correct number of rows, however the individual cells are empty. How do I properly bind them? Should I be using DataContext or ItemSource, and if so, where? Here's a single-column example of one such binding attempt.
<ListView ItemsSource="{Binding MyObservableCollection}">
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumnHeader Content="My String Data" />
<GridViewColumn.CellTemplate>
<DataTemplate>
<DataGridCell>
<TextBlock Text="{Binding Path=MyString}">
</TextBlock>
</DataGridCell>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
EDIT: Per Michal Ciechan, I've implemented INotifyPropertyChanged on my model class, but it didn't seem to change anything. I won't actually be changing the fields on these objects once they're in the collection, however, so this may not be the right approach. Here's some more example code.
The Model:
public class MyModel : INotifyPropertyChanged
{
public string MyString;
public event PropertyChangedEventHandler PropertyChanged;
}
The ViewModel:
ObservableCollection<MyModel> MyObservableCollection = new ObservableCollection<MyModel>();
public void AddModelToCollection()
{
MyModel mm = new MyModel();
mm.MyString = "HELLO WORLD";
MyObservableCollection.Add(mm);
}
public string MyString; is a field, not a property. You can't bind to fields. You can only bind to properties.
private string _myString;
public string MyString
{
get
{
return _myString;
}
set
{
_myString = value;
OnPropertyChanged("MyString");
}
}
I'll leave the implementation of OnPropertyChanged to you.
To clarify
This is a PROPERTY:
public string DERP { get; set; }
Notice it has a getter and a setter. The compiler turns this into two methods, one for getting the value and one for setting it.
This is a FIELD:
public string HERP;
Notice, it doesn't have a getter or a setter. It is just a pointer to a value on the stack.
Here comes the important bit:
In WPF, Bindings do NOT WORK with FIELDS. They only work with PROPERTIES
So setting the value of a field prior to attempting to bind against it matters not. The Binding won't be looking for fields, and therefore won't see it.
Will is right, your Model class needs to have MyString as a Property rather than field.
What is the difference between a Field and a Property in C#?
WPF - Binding - Binding Source
You can bind to public properties, sub-properties, as well as indexers, of any common language runtime (CLR) object. The binding engine uses CLR reflection to get the values of the properties. Alternatively, objects that implement ICustomTypeDescriptor or have a registered TypeDescriptionProvider also work with the binding engine.
For more information about how to implement a class that can serve as a binding source, see Implementing a Class for the Binding Source later in this topic.
public class MyModel : INotifyPropertyChanged
{
private string _myString;
public string MyString
{
get
{
return _myString;
}
set
{
_myString = value;
OnPropertyChanged("MyString");
}
}
}

changing the bound object in a datatemplate

Data templates are great, but I'm having a problem with binding in a particular situation. I have a class, Value, that has various descendants like StringValue, DateValue, etc. These Values show up in a Listbox. This template works fine, binding to a specific property of StringValue:
<DataTemplate DataType="{x:Type values:StringValue}">
<TextBox Margin="0.5"
Text="{Binding Path=Native}" />
</DataTemplate>
However, when I bind to an object itself, instead of a specific property, the changes don't update the object, as in this template:
<DataTemplate DataType="{x:Type values:LookupValue}">
<qp:IncrementalLookupBox SelectedValue="{Binding Path=., Mode=TwoWay}"
LookupProvider="{Binding ElementName=EditWindow, Path=ViewModel.LookupProvider}">
</qp:IncrementalLookupBox>
</DataTemplate>
IncrementalLookupBox is a UserControl that ultimately allows a user to select a LookupValue, which should replace the item bound in the template. If this was bound to a simple type like an int or string, the binding would replace the object, so I'm not sure what the difference is with a more complex object. I know that the IncrementalLookBox is working, because binding some textboxes to the properties of SelectedValue (which is a dependency property) shows the correctly selected LookupValue.
In case it makes the situation more clear, here is the implementation of SelectedValue:
public LookupValue SelectedValue
{
get { return (LookupValue)GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedValueProperty =
DependencyProperty.Register("SelectedValue", typeof(LookupValue), typeof(IncrementalLookupBox), new PropertyMetadata(OnSelectedValuePropertyChanged));
private static void OnSelectedValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var obj = d as IncrementalLookupBox;
obj.OnSelectedValuePropertyChanged(e);
}
private void OnSelectedValuePropertyChanged(DependencyPropertyChangedEventArgs e)
{
CheckForSelectedValueInLookups();
}
If all else fails consider using a ValueConverter to get the value you require.
Edit: this does not work. See link in comments below.
Make sure your class implements INotifyPropertyChanged and raise PropertyChanaged here:
private void OnSelectedValuePropertyChanged(DependencyPropertyChangedEventArgs e)
{
CheckForSelectedValueInLookups();
// RaisePropertyChanged();
}
My issue is the same as described here:
WPF TwoWay Binding of ListBox using DataTemplate
Apparently if I don't write enough text here, my answer will be converted to a comment and not close out the question. So, to summarize the issue, a two-way Binding=. in a datatemplate used in a ListBox (or any ItemsControl I image) won't work, because it is not the object itself being bound, but the ListBoxItem that contains it.

Categories

Resources