I am using the following xaml code:
<ListView ItemsSource="{Binding Offsets, Mode=TwoWay}">
<ListView.View>
<GridView>
<GridViewColumn Header="X" Width="90">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding X, Mode=TwoWay}"
Width="70"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
(I know this looks a little sloppy, but I am trying to keep it minimal)
The Offsets property I am binding to is a public List<Point3D> { get; set;}. (using System.Windows.Media.Media3D).
As such each Point3D has a public X,Y and Z property.
My ListView generates fine, but when I try to change a value of a TextBox, the Datacontext isn't updated.
What am I doing wrong?
If you are talking about Point3D Structure
Yes it has XYZ public properties but I don't think it implements INotifyPropertyChanged
Probably you forgot to implement the Interface INotifyPropertyChanged at your model or viewmodel classes.
You need to implement INotifyPropertyChanged interface in your class. Your binding mode is fine. You should be able to see changes.
public class YourClassName: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public ObservableCollection<YourModel> Offsets
{
get {return this.offsets;}
set
{
if (value != this.offsets)
{
this.offsets= value;
NotifyPropertyChanged("Offsets");
}
}
}
}
Related
To clarify what is going on. Basically I have a ListView binding which points to a List within an object. Within that same object (but not within the list) I have another list which holds strings used for a dropdown and I cannot assign it to my list view as the DataContext is already set to the first list mentioned. Can someone please offer a solution, or better yet a more efficient way to handle this?
View
<ListView ItemsSource="{Binding myModel.myCollection}" Grid.Row="1" Grid.Column="0">
<ListView.View>
<GridView>
<GridViewColumn Header="Name">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Name, Mode=TwoWay}"></TextBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Category Tag">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding myModel.CategoryList}"></ComboBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Model
public class SiteUrlsModel : INotifyPropertyChanged
{
public string CaseName { get; set; }
public List<string> TestList => new List<string> { "Test1", "Test2", "Test3" };
public List<string> _categoryTagList;
public List<string> CategoryTagList
{
get => _categoryTagList;
set
{
if (_categoryTagList == value)
return;
_categoryTagList = value;
OnPropertyChanged();
}
}
private ObservableCollection<SiteUrlsModel> _myCollection;
public ObservableCollection<SiteUrlsModel> myCollection
{
get => _siteurlscCollection;
set
{
if (_siteurlscCollection == value)
return;
_siteurlscCollection = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
For simplicity I have excluded the ViewModel and Code-Behind but after InitialiseComponent() I have DataContext = new TestViewModel() and in my ViewModel I have a property which creates a new instance of my Model as well as adding a getter to ensure everything is accessible. Rest assured the list gets populated I am simply trying to populate one dropdown separately.
This is happening because, the Combo Box's datacontext will be myModel's item.
You need to explicitly tell the combo box to get the itemssource from it's parent's datacontext.
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.myModel.CategoryList, RelativeSource={RelativeSource AncestorType=DataGrid}}"></ComboBox>
</DataTemplate>
I've read many topics that are really close to my question but none of them clarified me how to handle my problem.
In my WPF/MVVM app I have ListView in which there is GridView. I would like to have the first column as checkboxes. The ItemsSource of ListView is bind to MyItemsCollectionProperty (which is basically IEnumerable of MyClass instances). My ViewModel class has that property of course. I was able to bind all other columns from grid to properties of MyItemsCollectionProperty properties (I mean One, Two, Three) but not the checkbox column. So the question is how to bind my checkbox state directly to the property of MyClass in the collection (IsMarked boolean property)? I would appriciate any suggestions.
MyClass def:
namespace MyNamespace
{
public class MyClass
{
public bool IsMarked { get; set; }
public string One { get; set; }
public string Two { get; set; }
public string Three { get; set; }
}
}
And the XAML:
<ListView x:Name="listView" HorizontalAlignment="Left" Height="375" Margin="24,82,0,0" VerticalAlignment="Top" Width="750" ItemsSource="{Binding MyItemsCollectionProperty}">
<ListView.View>
<GridView>
<GridViewColumn Width="30">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<CheckBox x:Name="checkBoxSelect"/>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
<Grid>
<CheckBox x:Name="checkBoxSelectAll" ToolTip="Select all" IsChecked="{Binding SelectedAll}" Command="{Binding SelectAllCmd}"/>
</Grid>
</GridViewColumn>
<GridViewColumn Header="One" DisplayMemberBinding="{Binding One}"/>
<GridViewColumn Header="Two" DisplayMemberBinding="{Binding Two}"/>
<GridViewColumn Header="Three" DisplayMemberBinding="{Binding Three}"/>
</GridView>
</ListView.View>
</ListView>
And the piece of simplified ViewModel class code (not the whole class):
namespace MyNamespace.ViewModel
{
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private IEnumerable<MyClass> _myItemsCollectionProperty;
public IEnumerable<MyClass> MyItemsCollectionProperty
{
get { return _myItemsCollectionProperty; }
set
{
_myItemsCollectionProperty = value;
OnPropertyChanged(nameof(MyItemsCollectionProperty));
}
}
//(...)
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You need to implement INotifyPropertyChanged in MyClass to enable binding.
Check here how to impleement it https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-implement-property-change-notification
For those who will experience similar problem I have the solution (based on the XAMlMAX and partially Giltanas comments).
I am not sure if that is the best possible solution but what I've done is:
I left the MyClass almost as it was - I removed IsMarked property since it is strictly View-related - checkbox column (so I treat that class as it is strict Model class which instances are loaded by other Model class for loading them). No relation to View layer for now.
I created second class (let's call it MyClassVM) which is almost the same as MyClass. The difference is that this one has additional property IsMarked which is bound to checkbox column. Other difference is that this class implements INotifyPropertyChanged interface so it's strictly View-related.
There is async method in ViewModel for loading MyClass instance and then there is translantion from MyClass instance into MyClassVM. MyClassVM props are bound to the View.
Everything works as expected and I do believe I've got proper separation of BL (Model) and View/ViewModel. Correct me if I'm wrong. Thanks for all the suggestions. You guys helped a lot.
And funny but extremally important thing. What XAMlMAX suggested - after changes I've done I was given IntelliSense error in XAML but I ignored it and all the projects compiled and work as expected.
the XAML of my window:
<ListView Grid.Row="0" Name="files">
<ListView.Resources>
<DataTemplate x:Key="CheckboxTemplate">
<CheckBox IsChecked="{Binding Save, Mode=TwoWay}" />
</DataTemplate>
</ListView.Resources>
<ListView.View>
<GridView AllowsColumnReorder="False">
<GridViewColumn Header=" " Width="30" CellTemplate="{StaticResource CheckboxTemplate}" />
<GridViewColumn Header="Datei" DisplayMemberBinding="{Binding File}"/>
</GridView>
</ListView.View>
</ListView>
the constructor of my Window:
IEnumerable<SaveItem> sil = sdl.Select(d => new SaveItem() { Save = true, Document = d });
files.ItemsSource = sil;
and the datastructure i want to display:
public class SaveItem : INotifyPropertyChanged
{
private bool save;
public bool Save
{
get { return this.save; }
set
{
if (value != this.save)
{
this.save = value;
NotifyPropertyChanged("Save");
}
}
}
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public StandardDocument Document { get; set; }
public string File { get { return Document.Editor.File; } }
#region INotifyPropertyChanged Member
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
i call the window. The window appears. I uncheck a checkbox of an item of the listview. i click a button. in its event-handler i read out the itemssource of the listview and ... the Save-Property of the Unchecked Item is (in its source) still true!
where is my mistake? why does my sources not get updated if i check/uncheck a checkbox?
You have not set your data context. If you are all in the same class - put something like this in your constructor of the window.
DataContext = this;
I think you need to set the DataContext to the code behind and then for clarity bind to the path.
XAML to set the Window DataContext
DataContext="{Binding RelativeSource={RelativeSource Self}}"
try converting IEnumerable to list..
it is not suggested to use IEnumerable as item source particularly when item source is evaluated using Linq
List<SaveItem> sil = sdl.Select(d => new SaveItem() { Save = true, Document = d }).ToList<SaveItem>();
files.ItemsSource = sil;
I have some ObservableCollections binded to some WPF controls and they work fine. And I have a feature where I completely replace these ObservableCollections through reassignment and filling them again, but after doing this, the WPF controls don't get updated.
Or is this binding connection only established at startup once, and then I should never reinitialize the ObservableCollections, but only change them?
EDIT:
public partial class MainWindow : Window
{
ObservableCollection<EffectViewModel> effects;
public ObservableCollection<EffectViewModel> Effects
{
get { return this.effects; }
set
{
this.effects = value;
this.RaisePropertyChanged ( "Effects" );
}
}
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged ( string name )
{
var handler = this.PropertyChanged;
if ( handler != null )
handler ( this, new PropertyChangedEventArgs ( name ) );
}
}
public void LoadEffects ( string path, string filename )
{
//returns new ObservableCollection<EffectViewModel> ( );
this.Effects = File.Load ( path, filename );
}
public class EffectViewModel
{
public bool this [ EffectType type ]
{
get { return AllEffects.First ( e => e.Type == this.Type ).IsSupported; }
set
{
AllEffects.First ( e => e.Type == this.Type ).IsSupported = value;
this.RaisePropertyChanged ( "this" );
}
}
#region Events
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged ( string name )
{
var handler = this.PropertyChanged;
if ( handler != null )
handler ( this, new PropertyChangedEventArgs ( name ) );
}
#endregion
}
EDIT2:
<Window x:Class="EffectWindow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="Effect Display" Height="200" Width="700"
<DockPanel VerticalAlignment="Stretch">
<ListView
ItemsSource="{Binding Effects}"
AlternationCount="2"
DockPanel.Dock="Top"
HorizontalContentAlignment="Stretch">
<ListView.View>
<GridView>
<GridViewColumn
Width="70"
Header="GPU">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox
Margin="0"
HorizontalAlignment="Center"
IsChecked="{Binding [GPU], Mode=TwoWay}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn
Width="70"
Header="CPU">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox
Margin="0"
HorizontalAlignment="Center"
IsChecked="{Binding [CPU], Mode=TwoWay}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</DockPanel>
</Window>
The object you are binding to should implement the INotifyPropertyChanged interface. Then, the bound collection property should raise PropertyChanged event in its setter. Something like this:
public ObservableCollection<MyObject> MyCollection
{
get
{
return _myCollection;
}
set
{
_myCollection = value;
RaisePropertyChanged("MyCollection");
}
}
Try not to reassign, but clear and add new items.
You’ll need to know the dependency object and dependency property where the binding was defined. Then you can use this line:
BindingOperations.GetBindingExpressionBase(dependencyObject, dependencyProperty).UpdateTarget();
Question: Most code samples on the DataGridComboBox seem to use a static resource as the ItemsSource. In my use case, I'd like to provide different ItemsSources with each bound object. Can this be done?
Background: I'm trying to bind a collection of Question class objects to a WPF DataGrid, using a DataGridComboBoxColumn control. The Answer string provides the SelectedValue. I'd like the AnswerDomain list to provide the ItemsSource for each ComboBox. The AnswerDomain differs from Question to Question.
Class
public class Question
{
string Answer {get; set;}
List<string> AnswerDomain {get; set;}
//...other stuff
}
XAML
<DataGrid ItemsSource="{Binding Path=InspectionItems}" AutoGenerateColumns="False" Name="dataGrid1" >
<DataGrid.Columns>
<DataGridComboBoxColumn Header="Answer Domain"
DisplayMemberPath="Answer"
SelectedValuePath="Answer"
ItemsSource="{Binding Path=AnswerDomain}"
>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
Problem: There are a couple problems. The key issue right now is that the ComboBoxes in each DataGrid Row aren't displaying the AnswerDomain strings. I've tried a series of XAML combinations without success. Help me Stack Overflow.
UPDATE: The selected solution below worked. After some further fumbling and by adding UpdateSourceTrigger=PropertyChanged to the SelectedItem, user changes in the combobox were then reflected back in the underlying custom object.
<DataGridTemplateColumn Header="Answer">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding AnswerDomain}"
SelectedItem="{Binding Answer, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Your problem is that the display member path isn't Answer because there is no "Answer" property off of a string. I never use the DataGridComboBoxColumn, it doesn't seem natural to me, too much like the old win forms way. Try the below instead. BUT MAKE SURE YOU IMPLEMENT INotifyPropertyChanged on your Question Class, and fire the appropriate events.
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding AnswerDomain}" SelectedItem="{Binding Answer}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Here is how your Question class should look:
public class Question : INotifyPropertyChanged
{
private string m_Answer;
public string Answer
{
get { return m_Answer; }
set
{
if (m_Answer != value)
{
m_Answer = value;
FirePropertyChanged("Answer");
}
}
}
private List<string> m_AnswerDomain;
public List<string> AnswerDomain
{
get { return m_AnswerDomain; }
set
{
if (m_AnswerDomain != value)
{
m_AnswerDomain = value;
FirePropertyChanged("AnswerDomain");
}
}
}
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
private void FirePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}