WPF DataGridComboBoxColumn TextBinding not updating - c#

I am attempting to use a DataGridComboBoxColumn but I'm having trouble with displaying the text associated with the Selected Item. Technically, I'm using MaterialDataGridComboBoxColumn which extends DataGridComboBoxColumn (see that code here. The only real difference seems to be the addition of ItemSourceBinding which makes binding to a non static list easier.)
Here is the ComboBox in xaml:
<materialDesign:MaterialDataGridComboBoxColumn
Header="Meter"
ElementStyle="{StaticResource CenterEverything}"
ItemsSourceBinding="{Binding PotentialMeters}"
DisplayMemberPath="Name"
TextBinding="{Binding Segment.Meter.Name, Mode=OneWay}"
SelectedItemBinding="{Binding Segment.Meter, UpdateSourceTrigger=PropertyChanged}" />
The item source for the DataGrid is made of an ObservableCollection consisting of SegmentWrappers:
public sealed class SegmentWrapper : INotifyPropertyChanged {
public Segment Segment { get; set; }
public List<Meter> PotentialMeters => GetPotentialMeters();
public event PropertyChangedEventHandler PropertyChanged;
private List<Meter> GetPotentialMeters() => Segment.Station.AllMeters;
}
The Segment object is my actual model:
public sealed class Segment : INotifyPropertyChanged {
public Station Station { get; set; }
public Meter Meter { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
Now, the weird thing is I can get this to work sometimes when I'm running in debug. Normally, when I start up I cannot not see the name of the selected Meter in the ComboBox, but if I remove and re-add the TextBinding attribute from the xaml above while the code is running, the name is displayed correctly! I guess this has something to do with the UpdateSourceTrigger, but I cannot figure it out. Any ideas?

In the setter of your segment propeties, notify the property to reflect in the UI

I have a workaround for this now (so perhaps this should not be considered an answer? I'm new to SO, so I will happily post this as a comment if it should not be an answer).
I switched over to a DataGridTemplateColumn and basically used the same binding for everything and it worked. I changed my original xaml to this:
<DataGridTemplateColumn Header="Meter" Width="Auto" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Style="{StaticResource MaterialDesignDataGridComboBox}"
Foreground="{DynamicResource MaterialDesignBody}"
ItemsSource="{Binding PotentialMeters, Mode=OneWay}"
DisplayMemberPath="Name"
Text="{Binding Segment.Meter.Name, Mode=OneWay}"
SelectedItem="{Binding Segment.Meter}">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
I have no real answer as to why the original approach didn't work, but I'm happy with this.

Related

Listview Binding Does Not Update Programmatic Changes

I've done some research on the topic, and while I've come across some possibilities, nothing has worked for me.
Details:
I'm working on a WPF app using an MVVM design pattern. In the ViewModel, I have a List of Notes, a class with a few properties (among them, Note). I've created a property, SelectedNote on the VM to hold the currently selected note.
In my View, I've bound a ListView control to the list QcNotes. I've bound a TextBox to the SelectedNote property. When I make changes to the TextBox, they are correctly reflected in the appropriate row of the ListView.
Problem:
I've include a RevertChanges command. This is a relatively simple command that undoes changes I've made to the note. It correctly updates the TextBox, and it actually updates the underlying list correctly, but the changes do not update the ListView itself. (Is it necessary to use an ObservableCollection in this circumstance? I've been asked to try and resolve the problem without doing so).
Attempted Fixes
I tried to call NotifyPropertyChanged("SelectedNote") and NotifyPropertyChanged("QcNotes") directly from within the call to RevertChanges, but that hasn't fixed the problem.
Any ideas?
XAML
<Window.DataContext>
<VM:MainProjectViewModel />
</Window.DataContext>
<Grid>
<StackPanel>
<ListView ItemsSource="{Binding QcNotes, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" x:Name="list" SelectedItem="{Binding SelectedNote}">
<ListView.View>
<GridView>
<GridViewColumn Header="Note" DisplayMemberBinding="{Binding Note}" />
</GridView>
</ListView.View>
</ListView>
<TextBox
Height="30"
HorizontalAlignment="Stretch"
Text="{Binding SelectedNote.Note, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="Allow Edits" Command="{Binding ChangeStateToAllowEditsCommand}" />
<Button Content="Save Changes" Command="{Binding EditNoteCommand}" />
<Button Content="Revert Changes" Command="{Binding RevertChangesToNoteCommand}" />
</StackPanel>
</StackPanel>
</Grid>
ViewModel Code
public class MainViewModel : BaseViewModel
{
private QcNote selectedNote;
private string oldNoteForUpdating;
private VMState currentState;
private string noteInput;
private IList<QcNote> qcNotes;
public IList<QcNote> QcNotes
{
get
{
return qcNotes;
}
set
{
qcNotes = value;
NotifyPropertChanged();
}
}
public QcNote SelectedNote
{
get
{
return selectedNote;
}
set
{
selectedNote = value;
oldNoteForUpdating = SelectedNote.Note;
NotifyPropertChanged();
}
}
public VMState CurrentState
{
get
{
return currentState;
}
set
{
currentState = value;
NotifyPropertChanged();
}
}
public ICommand RevertChangesToNoteCommand
{
get
{
return new ActionCommand(o => RevertChangestoNote());
}
}
private void RevertChangestoNote()
{
QcNotes.First(q => q.Id == SelectedNote.Id).Note = oldNoteForUpdating;
SelectedNote.Note = oldNoteForUpdating;
NotifyPropertChanged("SelectedNote");
NotifyPropertChanged("QcNotes");
CurrentState = VMState.View;
}
I'll post an answer to my own question, but don't want to deter other from offering suggestions.
I implemented the INotifyPropertyChanged interface on my Models.QcNote class, and that resolved the issue. Initially, the interface was implemented exclusively on the ViewModel. In that case, NotifyPropertyChanged was only called when the QcNote object itself was changed, not when the properties of the object were changed.

Binding ICommand WPF ObservableCollection DataGrid

I'm trying to get a WPF MVVM template to work with the basic functionality I am doing in a WPF but non MVVM application. In this case I am trying to capture the RowEditEnding event (which I am) to validate the data on the row that has changed (and this is the problem).
In the XAML I have used an event trigger:
<DataGrid AutoGenerateColumns="False" HorizontalAlignment="Stretch" Margin="0,0,0,0" VerticalAlignment="Stretch"
ItemsSource="{Binding oDoc.View}">
<DataGrid.Columns>
<DataGridTextColumn x:Name="docIDColumn" Binding="{Binding DocId}" Header="ID" Width="65"/>
<DataGridTextColumn x:Name="DocumentNumberColumn" Binding="{Binding Number}" Header="Document Number" Width="*"/>
<DataGridTextColumn x:Name="altIDColumn" Binding="{Binding AltID, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Alt" Width="55"/>
</DataGrid.Columns>
<i:Interaction.Triggers>
<i:EventTrigger EventName="RowEditEnding">
<i:InvokeCommandAction Command="{Binding DocRowEdit}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
With a delegate command to rout to the handler:
public ObservableCollection<Document> oDoc
{
get
{
return _oDoc;
}
}
public ICommand DocRowEdit
{
get { return new DelegateCommand(DocumentRowEditEvent); }
}
public void DocumentRowEditEvent()
{
//How do I find the changed item?
int i = 1;
}
I have not found a way to find the member of the ObservableCollection (oDoc) that has pending changes. I notice that the datagrid is doing some validation, the AltID field that I want to change will highlight red if I put in a non numeric value. But I want to handle the validation, and associated messaging myself. What am I missing? I was thinking to somehow raise a property changed event, but don't find how to wire something like this in:
protected void RaisePropertyChangedEvent(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
The last two code blocks are from my ViewModel class, and I'm trying to do this without any code behind, aside from instantiating the ViewModel in the MainWindow constructor.
You can add a property to your ViewModel:
public Document CurrentDocument
{
get
{
return _currentDocument;
}
set
{
if (value != _currentDocument)
{
_currentDocument = value;
OnPropertyChanged("CurrentDocument") // If you implement INotifyPropertyChanged
}
}
}
and then you can bind it to SelectedItem property of your DataGrid:
<DataGrid AutoGenerateColumns="False" HorizontalAlignment="Stretch" Margin="0,0,0,0" VerticalAlignment="Stretch"
ItemsSource="{Binding oDoc.View}" SelectedItem="{Binding CurrentDocument}">
Therefore your edit method will be:
public void DocumentRowEditEvent()
{
CurrentDocument.Number = DateTime.Now.Ticks;
/* And so on... */
}
I hope it helps.

WPF Databinding with a ComboBox

I'm trying to learn WPF Databinding with Entity Framework. I have implemented the tutorial in the link
and it works perfectly fine. I'm trying to insert a combo box myself and I want to bind it to category's name. But I couldn't achieve it.
Here is my attempt :
on XAML file :
<ComboBox HorizontalAlignment="Left" Margin="394,421,0,0" VerticalAlignment="Top" Width="120" Name="ComboCategory" Binding="{Binding Name}" />
and code-behind :
ComboCategory.ItemSource = _context.Categories.Local.ToList();
Can you tell me what I'm missing? Thanks.
Altough the use of the ItemSource is perfectly valid. I suggest you work with Data Binding. Here's a nice definition from MSDN:
Data binding is the process that establishes a connection between the application UI and business logic. If the binding has the correct settings and the data provides the proper notifications, then, when the data changes its value, the elements that are bound to the data reflect changes automatically. Data binding can also mean that if an outer representation of the data in an element changes, then the underlying data can be automatically updated to reflect the change. For example, if the user edits the value in a TextBox element, the underlying data value is automatically updated to reflect that change.
I have answered a question where someone also had a problem with binding items to a ListBox. This is not a ComboBox but the principle is the same. Click here to go the question, and here to go straight to the answer.
Basically it comes down to this:
Set up your UI
Bind your data
In following code, I changed the properties a bit according to the properties used in the tutorial.
XAML:
<ListBox Margin="20" ItemsSource="{Binding Products}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=ProductId}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
C#
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
}
public class ProductViewModel
{
public List<Product> Products
{
get
{
return new List<Product>
{
new Product{ ProductId = 1, Name = "Product_1" },
new Product{ ProductId = 2, Name = "Product_2" }
};
}
}
}
//Following code can be placed in the Loaded event of the page:
DataContext = new ProductViewModel();
You are missing DisplayMemberpath property here
<ComboBox HorizontalAlignment="Left" Margin="394,421,0,0" VerticalAlignment="Top" Width="120" Name="ComboCategory" DisplayMemberPath = "Name" />
It worked when I used this in XAML:
<ComboBox HorizontalAlignment="Left" Margin="394,421,0,0" VerticalAlignment="Top" Width="120" Name="ComboCategory" DisplayMemberPath = "Name" ItemsSource="{Binding}" />
Couldnt believe it, check the link
The issue is ItemsSource(plural) not ItemSource(singular)

Reference Implementation for MVVM with Hierarchical Objects

I read a couple of MVVM Tutorials (1,2,3,4) but I can't find a proper answer to my problem.
My model is a hierarchical tree like this:
public class MyModel {
public string Name { get; set; }
public Guid Id { get; set; }
public List<MyModel> Children { get; set; }
}
Now I want to display this in a TreeView like this (each entry if from the type MyModel):
Root
|-SubElement
|-SubElement2
| |-SubSubElement1
|-SubElement3
Now my questions:
How would the corresponding View-Model look like? Is there a reference implementation for Collections?
Should the Model or the View-Model or both implement INotifyPropertyChanged, INotifyCollectionChanged or should the List of Children be of the Type ObservableCollection<MyModel>? If so, when to call OnPropertyChanged()?
Now for the displaying:
I would like to display the Name of the object in the TreeView, but when selecting the element, I want to be notified of that event (and get the corresponding element). The ViewModel must somehow support this. Something like holding a list (say MyModelList) to which I can bind in XAML, like:
<TreeView ...
ItemsSource="{Binding ElementName=MyModelList, Path=SelectedItem.Name}"
... >
and where I can use InputBindings or EventTriggers.
Try using Hierarchical data template for showing tree structure within TreeView
<Grid>
<Grid.Resources>
<HierarchicalDataTemplate ItemsSource="{Binding Children}"
DataType="{x:Type local:MyModel}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</Grid .Resources>
<TreeView ItemsSource="{Binding MyModelList}"/>
</Grid>
Go through this MSDN Blog to get a better understanding - TreeView and HierarchicalDataTemplate, Step-by-Step
How would the corresponding View-Model look like?
In this situation will be Model and the collection, which will be in the ViewModel.
Should the Model or the View-Model or both implement INotifyPropertyChanged
I advise you to use ObservableCollection<T>, because all changes for Collection will automatically (add, remove, etc) appear in it. Properties that will be in Model must implement INotifyPropertyChanged interface. I personally do it on the side of the Model, but there are opponents of this idea, so where to implement it - your personal desire.
Example of this idea:
Model
public class MyModel : NotificationObject // he implement INotifyPropertyChanged
{
...
}
ViewModel
public ObservableCollection<MyModel> MyObjects
{
get;
set;
}
// in Constructor of ViewModel
MyLogObjects = new ObservableCollection<MyModel>();
When selecting the element, I want to be notified of that event
In WPF and Silverlight TreeView.SelectedItem is a readonly property. In this case you can see this example:
<StackPanel x:Name="LayoutRoot">
<StackPanel.Resources>
<sdk:HierarchicalDataTemplate x:Key="ChildTemplate" ItemsSource="{Binding Path=Children}" >
<TextBlock Text="{Binding Path=Name}" />
</sdk:HierarchicalDataTemplate>
<sdk:HierarchicalDataTemplate x:Key="NameTemplate"
ItemsSource="{Binding Path=Children}"
ItemTemplate="{StaticResource ChildTemplate}">
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold" />
</sdk:HierarchicalDataTemplate>
</StackPanel.Resources>
<sdk:TreeView x:Name="myTreeView"
Width="400"
Height="300"
ItemsSource="{Binding HierarchicalAreas}"
ItemTemplate="{StaticResource NameTemplate}"
local:Attached.TreeViewSelectedItem="{Binding SelectedArea, Mode=TwoWay}" />
</StackPanel>
Prefix sdk: needed for Silverlight
For notified event you can create an PropertyChangedEventHandler event handler for ViewModel in constructor, to know that the SelectedItem changed:
public MyViewModel()
{
MyModel = new MyModel();
MyModel.PropertyChanged += new PropertyChangedEventHandler(MyModel_PropertyChanged);
}
private void MyModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName.Equals("SelectedItem"))
{
System.Diagnostics.Debug.WriteLine("SelectedItem changed");
}
}

WPF Data Binding an ObservableCollection (or LinkedList) to a WrapPanel

Ok. First of all, I've looked at a bunch of other questions and a ton of other places on the internet on how to do this, and none of them are helping, so please don't mark this as a duplicate, and also, heads up cause I probably made a really stupid mistake.
I'm trying to bind an ObservableCollection to a WrapPanel using an ItemsControl and a DataTemplate. The following is my XAML Code:
<ItemsControl x:Name="wPanel">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<!--<Border BorderBrush="DarkGray" Background="Transparent">-->
<StackPanel MinWidth="250">
<Grid>
<TextBlock Text="{Binding address}" />
</Grid>
<Grid>
</Grid>
</StackPanel>
<!--</Border>-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Note: I did have (for the ItemsSource property of ItemsControl) {Binding properties}
This is my declaration of properties:
public ObservableCollection<Property> properties = new ObservableCollection<Property>();
And the Property Class is the following plus many more properties:
private string address { get; set; }
private string city { get; set; }
private string postcode { get; set; }
private double price { get; set; }
private LinkedList<Tennant> tennants { get; set; }
...
I thought I had solved the problem with this,
Binding binding = new Binding();
binding.Source = properties;
wPanel.SetBinding(ItemsControl.ItemsSourceProperty, binding);
But, then the line in the xaml: <TextBlock Text="{Binding address}" /> didn't work.
Then I came to the conclusion that it had to do with the properties object, and how it wouldn't bind unless I did it through code.
What am I doing wrong, for it not bind through XAML, etc.? What do I need to change about the properties object, or what do I need to do?
Thanks in advance.
You can bind ItemsControl's ItemsSource to properties just like #AjS answer. But before that, you need to change properties declaration to be property instead of field.
public ObservableCollection<Property> properties { get; set; }
And also address property of your Property class need to be public.
public string address { get; set; }
Isn't the 'properties' a property on the window?
If you are binding in xaml, make sure you use declare 'properties' as 'Property', set datacontext of window to itself and then set binding path:-
<ItemsControl x:Name="wPanel" ItemsSource="{Binding properties}">
this.DataContext=this; //set datacontext on parent window or control
If you are doing it in code, setting the ItemsSource directly on wPanel should work:-
wPanel.ItemsSource=properties;
This is easy trick for check Binding. Every WPF developer must know this. For example:
<TextBlock Text="{Binding}" />
Run and see it.
If TextBlock has any object in DataContext, object class name displayed in TextBlock.
If DataContext has empty. TextBlock is Empty

Categories