WPF two way binding with explicit source updating is not working - c#

I have binding ObservableCollection - DataGrid (mode - TwoWay), but i want update collection by myself with UpdateSource() call and disable automathic source updating. I set binding like
ItemsSource="{Binding Path=Bezier.BezierPoints, Mode=TwoWay, UpdateSourceTrigger=Explicit}"
but my collection still updates automathically. My code samples are below. What am i doing wrong?
My XAML:
<DataGrid Name="BezierPointsDataGrid" Margin="5" AutoGenerateColumns="False"
Grid.Column="0" Grid.Row="0" Background="White"
ItemsSource="{Binding Path=Bezier.BezierPoints, Mode=TwoWay, UpdateSourceTrigger=Explicit}">
<DataGrid.Columns>
<DataGridTextColumn Header="X" Binding="{Binding Path=X}" Width="1*"/>
<DataGridTextColumn Header="Y" Binding="{Binding Path=Y}" Width="1*"/>
</DataGrid.Columns>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding Path=UpdateBezierPointsCommand}" CommandParameter="{Binding ElementName=BezierPointsDataGrid}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
My ViewModel:
class BezierCurveViewModel : INotifyPropertyChanged
{
#region Bezier curve model
private BezierCurveModel _bezier;
public BezierCurveModel Bezier
{
get { return _bezier; }
set
{
if (_bezier == value)
return;
_bezier = value;
OnPropertyChanged("Bezier");
}
}
#endregion
#region Commands
public ICommand UpdateBezierPointsCommand { set; get; }
#endregion
#region Constructor
public BezierCurveViewModel()
{
UpdateBezierPointsCommand = new Command(a => ((DataGrid)a).GetBindingExpression(DataGrid.ItemsSourceProperty).UpdateSource());
Bezier = new BezierCurveModel();
}
#endregion
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
My model:
public ObservableCollection<DPoint> BezierPoints { private set; get; }
EDIT: I changed ObservableCollection To DataTable to achieve expected behaviour. But i am still interested in solving this problem because i want to understand why any binding to observable collection updates source after editing table (read my comment to Andrew's post).

Here, you have set up the view to update the BezierPoints property explicitly, since that is what you are binding the ItemsSource.
I will assume that what you actually want is to use an Explicit update trigger on the properties of the individual points. To do this, you need to change the DataGridTextColum binding to UpdateSourceTrigger=Explicit.
As a side note, it would seem impossible that you could ever update the BezierPoints collection from the View at all because the property has a private setter.

Related

WPF MVVM Listview : Is Leftdoubleclick need write event?

I wrote update function, I want to when I double-click a data in the listview, data will be shown in a textbox. I search and find many solutions,
I have a example:
`<ListView ItemsSource="{Binding listHocVien}"
SelectedItem="{Binding SelectedItem, Mode=OneWayToSource}">
<ListView.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" />
</ListView.InputBindings>
<ListView.View>
<GridView>
<GridViewColumn Header="Name"
DisplayMemberBinding="{Binding Name}">
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>`
But I when I run the app and click data, I just need one click, not double-click.
I have to find the solution on the internet and didn't see anyone said to write an event for LeftDoubleClick.
So, did we need to write the event to LeftDoubleClick? If yes, can anyone show me examples.
Thank for all your help.
You could use behaviours:
How to add System.Windows.Interactivity to project?.
This way you could create a double click command and bind it to your view model class. In the execute of your command you could set the property of the textbox to the desired text
After you've added in your project you should reference the namespace in the xaml code. If you reference it as i then your code to add the behaviour to the list view should be as follows:
In your xaml:
<TextBox Text ="{Binding Text, UpdateSourceTrigger=PropertyChanged}"/>
<ListView>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding YourCommand}"
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListView>
In your View Model:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
public class SampleViewModel : INotifyPropertyChanged {
private string _Text;
public event PropertyChangedEventHandler PropertyChanged;
public string Text {
get { return _Text; }
set {
if (_Text != value) {
_Text = value;
RaisePropertyChanged();
}
}
}
public ICommand YourCommand { get; set; }
public SampleViewModel() {
YourCommand = new RelayCommand<TType>(YourCommandExecute); // that TType is the type of your elements in the listview
}
// Here I will assume that your TType has a property named Description
private void YourCommandExecute(TType selectedElement) {
Text = selectedItem.Description;
}
public void RaisePropertyChanged([CallerMemberName] propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Relay Command Implementation
// Simple Implementation of Generic Relay Command:
public class RelayCommand<T> : ICommand
{
private Action<T> execute;
private Func<T,bool> canExecute;
public event EventHandler CanExecuteChanged;
public RelayCommand(Action<T> execute,Func<T,bool> canExecute=null)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return canExecute == null || canExecute((T)parameter);
}
public void Execute(object parameter)
{
execute((T)parameter);
}
}

bind a textbox to a property

All I want is when a user changes the value in the textbox alphaMin_txt, the property AlphaMin gets updated.
Code behind:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string _alphaMin;
public string AlphaMin
{
get { return _alphaMin; }
set
{
if (value != _alphaMin)
{
_alphaMin = value;
NotifyPropertyChanged();
}
}
}
}
XAML:
<DockPanel DataContext="{Binding MainWindow}">
<TextBox Text="{Binding
Path=AlphaMin,
NotifyOnTargetUpdated=True,
Mode=OneWayToSource,
UpdateSourceTrigger=PropertyChanged}" />
</DockPanel>
This should be a duplicate a hundred times over but I've been through it all and none of it is laid out plain and simple for this one-way update of the source. All the MSN tutorials are binding some UIControl to another, which is pointless because IntelliSense shows you how to do that.
Your DockPanel probably has a faulty DataContext binding. DataContext should be set at the window level.
<Window ... DataContext="{Binding RelativeSource={RelativeSource Self}}" ..>
Of course, this is assuming your XAML is MainWindow.xaml.
If you have a different DataContext for the rest of the MainWindow, then you can do this:
<TextBox Text="{Binding
RelativeSource={RelativeSource AncestorType=Window},
Path=AlphaMin,
NotifyOnTargetUpdated=True,
Mode=OneWayToSource,
UpdateSourceTrigger=PropertyChanged}" />
Of course, you should remove the DataContext for the DockPanel.
Your code behind is correct; there are no changes needed. Using CallerMemberName is a good way to implement INotifyPropertyChanged.
Assign a name to <Window x:Name="MyWin"...> , then change DataContext binding to {Binding ElementName=MyWin}.
Change this NotifyPropertyChanged(); to NotifyPropertyChanged("AlphaMin");

How to get my ViewModel instantiated

I have an application that works, and now I'm trying to do it with proper MVVM. As I understand it from many months of reading, the LinqToSQL classes (by VS2013) are my model, as is the SQL Server database that it uses. The UI is the view, and I am implementing some ObservableCollections as my view model. So here is the ViewModel:
partial class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
static DataClasses1DataContext _dataDc = new DataClasses1DataContext();
ObservableDocuments _oDoc = new ObservableDocuments(_dataDc);
public ObservableCollection<Document> oDoc
{
get
{
return _oDoc;
}
}
public ICommand LoadData
{
get;
private set;
}
protected void RaisePropertyChangedEvent(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
In the working version, the DataContext and ObservableDocuments lines were in the MainWindow code . . . so I have moved them. Here is the xaml:
<Window x:Class="LINQ_MVVM_1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModel="clr-namespace:LINQ_MVVM_1.ViewModel"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid AutoGenerateColumns="False" HorizontalAlignment="Stretch" Margin="0,0,0,0" VerticalAlignment="Stretch"
DataContext="{Binding RelativeSource={RelativeSource AncestorType=Window}}"
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}" Header="Alt" Width="55"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
And here is the code for the binding reference object, a ViewableCollection, with a filterable view property:
public class ViewableCollection<T> : ObservableCollection<T>
{
private ListCollectionView _View;
public ListCollectionView View
{
get
{
if (_View == null)
{
_View = new ListCollectionView(this);
}
return _View;
}
}
}
class ObservableDocuments : ViewableCollection<Document>
{
public ObservableDocuments(DataClasses1DataContext dataDc)
{
foreach (Document doc in dataDc.Documents)
{
this.Add(doc);
}
}
}
Moving the _dataDc and _oDoc into the ViewModel class has resulted in nothing populating the data grid. What am I doing wrong? It seems that getting the ViewModel instantiated is not happening.
This line:
DataContext="{Binding RelativeSource={RelativeSource AncestorType=Window}}"
Means that you are still looking at the code-behind for your DataContext. In case you aren't aware, the DataContext defines the root object to which all bindings look at the start of their "Path".
So you're right, the VM isn't being instantiated because you never instantiated it.
Instead, remove that line and in your UI constructor write:
DataContext = new MainViewModel();
There are other ways to do it of course, but thats the simplest.

Binding the ItemsSource for a WPF DataGridComboBox Column

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));
}
}

Wpf Combobox in Master/Detail MVVM

I have MVVM master /details like this:
<Window.Resources>
<DataTemplate DataType="{x:Type model:EveryDay}">
<views:EveryDayView/>
</DataTemplate>
<DataTemplate DataType="{x:Type model:EveryMonth}">
<views:EveryMonthView/>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox Margin="12,24,0,35" Name="schedules"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Elements}"
SelectedItem="{Binding Path=CurrentElement}"
DisplayMemberPath="Name" HorizontalAlignment="Left" Width="120"/>
<ContentControl Margin="168,86,32,35" Name="contentControl1"
Content="{Binding Path=CurrentElement.Schedule}" />
<ComboBox Height="23" Margin="188,24,51,0" Name="comboBox1"
VerticalAlignment="Top"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Schedules}"
SelectedItem="{Binding Path=CurrentElement.Schedule}"
DisplayMemberPath="Name"
SelectedValuePath="ID"
SelectedValue="{Binding Path=CurrentElement.Schedule.ID}"/>
</Grid>
This Window has DataContext class:
public class MainViewModel : INotifyPropertyChanged {
public MainViewModel() {
elements.Add(new Element("first", new EveryDay("First EveryDay object")));
elements.Add(new Element("second", new EveryMonth("Every Month object")));
elements.Add(new Element("third", new EveryDay("Second EveryDay object")));
schedules.Add(new EveryDay());
schedules.Add(new EveryMonth());
}
private ObservableCollection<ScheduleBase> _schedules = new
ObservableCollection<ScheduleBase>();
public ObservableCollection<ScheduleBase> Schedules {
get {
return _schedules;
}
set {
schedules = value;
this.OnPropertyChanged("Schedules");
}
}
private Element _currentElement = null;
public Element CurrentElement {
get {
return this._currentElement;
}
set {
this._currentElement = value;
this.OnPropertyChanged("CurrentElement");
}
}
private ObservableCollection<Element> _elements = new
ObservableCollection<Element>();
public ObservableCollection<Element> Elements {
get {
return _elements;
}
set {
elements = value;
this.OnPropertyChanged("Elements");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
One of Views:
<UserControl x:Class="Views.EveryDayView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid >
<GroupBox Header="Every Day Data" Name="groupBox1" VerticalAlignment="Top">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TextBox Name="textBox2" Text="{Binding Path=AnyDayData}" />
</Grid>
</GroupBox>
</Grid>
My SelectedItem in ComboBox doesn't works correctly. Are there any visible errors in my code?
What I usually do is bind the items of an ItemsControl to an ICollectionView (usually ListCollectionView) instead of directly to a collection; I think that's what the ItemsControl does by default anyway (creates a default ICollectionView), but I might be wrong.
Anyway, this allows you to work with the CurrentItem property of the ICollectionView, which is automatically synchronized with the selected item in an ItemsControl (if the IsSynchronizedWithCurrentItem property of the control is true or null/default). Then, when you need the current item in the ViewModel, you can use that instead. You can also set the selected item by using the MoveCurrentTo... methods on the ICollectionView.
But as I re-read the question I realize you may have another problem altogether; you have a collection of 'default' items and need a way to match them to specific instances. It would however be a bad idea to override the equality operators of the objects to consider them always equal if they are of the same type, since that has the potential to make other code very confusing. I would consider extracting the type information into an enum, and put a read-only property on each object returning one of the enum values. Then you can bind the items to a collection of the enum values, and the selected item to the enum property of each object.
Let me know if you need an example, I may have made a mess of the explanation :)

Categories

Resources