Viewmodel object does not receive database source changes - sql-server

New to WPF, MVVM, data binding, and Entity Framework, so apologies in advance.
I'm attempting to bind WPF controls to my database-generated model objects through a viewmodel. I'm able to change database values by typing in the textbox, but any direct changes to the database rows do not seem to fire off the PropertyChanged event. At least they are not reflected in my viewmodel objects. I've implemented iNotifyPropertyChanged on my Entity Framework generated classes thus:
public partial class GenGround : INotifyPropertyChanged
{
public double ClMax
{
get { return (double)this.clMax; }
set
{
this.clMax = (float)value;
MainWindow.db.SaveChanges();
OnPropertyChanged("ClMax");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string Property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(Property));
}
}
}
I have a listbox:
<DataTemplate x:Key="missionLegTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
<ListBox x:Name="missionList" SelectionChanged="missionList_SelectionChanged" Grid.ColumnSpan="2" ItemsSource="{Binding Mode=OneWay}" ItemTemplate="{DynamicResource missionLegTemplate}" />
private void MainWindow1_Loaded(object sender, RoutedEventArgs e)
{
this.missionList.ItemsSource = _CurrentMissionProfile.MissionLegs;
}
"MissionLegs" is an ObservableCollection of MissionLeg objects, attached to the database. The selected item of this listbox should tell the textboxes what properties to get and set. Textbox:
<TextBox x:Name="velocityBox" Text="{Binding Mode=TwoWay,Path=SelectedItem.Velocity, ElementName=missionList}" IsEnabled="False" />
As I said, this seems to write to the database, but when I make changes to the corresponding row in SSMS, nothing seems to happen. Ideas?

Related

WPF dynamic binding to property

I have an ItemsControl that should display the values of some properties of an object.
The ItemsSource of the ItemsControl is an object with two properties: Instance and PropertyName.
What I am trying to do is displaying all the property values of the Instance object, but I do not find a way to set the Path of the binding to the PropertyName value:
<ItemsControl ItemsSource={Binding Path=InstanceProperties}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=PropertyName, Mode=OneWay}"/>
<TextBlock Text=": "/>
<TextBlock Text="{Binding Source=??{Binding Path=Instance}??, Path=??PropertyName??, Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
the question marks are the points where I don't know how to create the binding.
I initially tried with a MultiValueConverter:
<TextBlock Grid.Column="1" Text="{Binding}">
<TextBlock.DataContext>
<MultiBinding Converter="{StaticResource getPropertyValue}">
<Binding Path="Instance" Mode="OneWay"/>
<Binding Path="PropertyName" Mode="OneWay"/>
</MultiBinding>
</TextBlock.DataContext>
</TextBlock>
The MultiValueConverter uses Reflection to look through the Instance and returns the value of the property.
But if the property value changes, this change is not notified and the displayed value remains unchanged.
I am looking for a way to do it with XAML only, if possible, if not I will have to write a wrapper class to for the items of the ItemsSource collection, and I know how to do it, but, since it will be a recurring task in my project, it will be quite expensive.
Edit:
For those who asked, InstanceProperties is a property on the ViewModel which exposes a collection of objects like this:
public class InstanceProperty : INotifyPropertyChanged
{
//[.... INotifyPropertyChanged implementation ....]
public INotifyPropertyChanged Instance { get; set; }
public string PropertyName { get; set; }
}
Obviously the two properties notify theirs value is changing through INotifyPropertyChanged, I don't include the OnPropertyChanged event handling for simplicity.
The collection is populated with a limited set of properties which I must present to the user, and I can't use a PropertyGrid because I need to filter the properties that I have to show, and these properties must be presented in a graphically richer way.
Thanks
Ok, thanks to #GazTheDestroyer comment:
#GazTheDestroyer wrote: I cannot think of any way to dynamically iterate and bind to an arbitrary object's properties in XAML only. You need to write a VM or behaviour to do this so you can watch for change notifications, but do it in a generic way using reflection you can just reuse it throughout your project
I found a solution: editing the ViewModel class InstanceProperty like this
added a PropertyValue property
listen to PropertyChanged event on Instance and when the PropertyName value changed is fired, raise PropertyChanged on PropertyValue
When Instance or PropertyName changes, save a reference to Reflection's PropertyInfo that will be used by PropertyValue to read the value
here is the new, complete, ViewModel class:
public class InstanceProperty : INotifyPropertyChanged
{
#region Properties and events
public event PropertyChangedEventHandler PropertyChanged;
private INotifyPropertyChanged FInstance = null;
public INotifyPropertyChanged Instance
{
get { return this.FInstance; }
set
{
if (this.FInstance != null) this.FInstance.PropertyChanged -= Instance_PropertyChanged;
this.FInstance = value;
if (this.FInstance != null) this.FInstance.PropertyChanged += Instance_PropertyChanged;
this.CheckProperty();
}
}
private string FPropertyName = null;
public string PropertyName
{
get { return this.FPropertyName; }
set
{
this.FPropertyName = value;
this.CheckProperty();
}
}
private System.Reflection.PropertyInfo Property = null;
public object PropertyValue
{
get { return this.Property?.GetValue(this.Instance, null); }
}
#endregion
#region Private methods
private void CheckProperty()
{
if (this.Instance == null || string.IsNullOrEmpty(this.PropertyName))
{
this.Property = null;
}
else
{
this.Property = this.Instance.GetType().GetProperty(this.PropertyName);
}
this.RaisePropertyChanged(nameof(PropertyValue));
}
private void Instance_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == this.PropertyName)
{
this.RaisePropertyChanged(nameof(PropertyValue));
}
}
private void RaisePropertyChanged(string propertyname)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
#endregion
}
and here is the XAML:
<ItemsControl ItemsSource={Binding Path=InstanceProperties}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=PropertyName, Mode=OneWay}"/>
<TextBlock Text=": "/>
<TextBlock Text="{Binding Path=PropertyValue, Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

WPF - Dependency Property of Custom Control lost Binding at 2 way mode

I have this Custom Control
XAML:
<UserControl x:Class="WpfApplication1.UC"
...
x:Name="uc">
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Horizontal">
<TextBox Text="{Binding Test, ElementName=uc}" Width="50" HorizontalAlignment="Left"/>
</StackPanel>
</UserControl>
C#
public partial class UC : UserControl
{
public static readonly DependencyProperty TestProperty;
public string Test
{
get
{
return (string)GetValue(TestProperty);
}
set
{
SetValue(TestProperty, value);
}
}
static UC()
{
TestProperty = DependencyProperty.Register("Test",typeof(string),
typeof(UC), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
}
public UC()
{
InitializeComponent();
}
}
And this is how i used that custom control:
<DockPanel>
<ItemsControl ItemsSource="{Binding Path=DataList}"
DockPanel.Dock="Left">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" CommandParameter="{Binding}" Click="Button_Click"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<local:UC Test="{Binding SelectedString, Mode=OneWay}"/>
</DockPanel>
--
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
private ObservableCollection<string> _dataList;
public ObservableCollection<string> DataList
{
get { return _dataList; }
set
{
_dataList = value;
OnPropertyChanged("DataList");
}
}
private string _selectedString;
public string SelectedString
{
get { return _selectedString; }
set
{
_selectedString = value;
OnPropertyChanged("SelectedString");
}
}
public MainWindow()
{
InitializeComponent();
this.DataList = new ObservableCollection<string>();
this.DataList.Add("1111");
this.DataList.Add("2222");
this.DataList.Add("3333");
this.DataList.Add("4444");
this.DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.SelectedString = (sender as Button).CommandParameter.ToString();
}
}
If I do not change text of UC, everything is ok. When I click each button in the left panel, button's content is displayed on UC.
But when I change text of UC (ex: to 9999), Test property lost binding. When I click each button in the left panel, text of UC is the same that was changed (9999). In debug I see that SelectedString is changed by each button click but UC's text is not.
I can 'fix' this problem by using this <TextBox Text="{Binding Test, ElementName=uc, Mode=OneWay}" Width="50" HorizontalAlignment="Left"/> in the UC.
But I just want to understand the problem, can someone help me to explain it please.
Setting the value of the target of a OneWay binding clears the binding. The binding <TextBox Text="{Binding Test, ElementName=uc}" is two way, and when the text changes it updates the Test property as well. But the Test property is the Target of a OneWay binding, and that binding is cleared.
Your 'fix' works because as a OneWay binding, it never updates Test and the binding is never cleared. Depending on what you want, you could also change the UC binding to <local:UC Test="{Binding SelectedString, Mode=TwoWay}"/> Two Way bindings are not cleared when the source or target is updated through another method.
The issue is with below line
<local:UC Test="{Binding SelectedString, Mode=OneWay}"/>
The mode is set as oneway for SelectString binding so text will be updated when the value from code base changes. To change either the source property or the target property to automatically update the binding source as TwoWay.
<local:UC Test="{Binding SelectedString, Mode=TwoWay}"/>

update GUI for SelectedeRow values of wpf datagrid

I have a wpf (.Net 4.5) datagrid. I am using the MVVM pattern for my application with the MVVM-Light framework.
I have a datagrid that is bound to an observable collection of "Tracking" objects called TrackingCollection. The datagrid selectedItem is bound to a "SelectedTracking" property in the viewModel.
<DataGrid Grid.Column="1" Grid.Row="3" Grid.ColumnSpan="3" MinHeight="300"
ItemsSource="{Binding TrackingCollection}"
CanUserAddRows="False" CanUserDeleteRows="False"
SelectionMode="Single" SelectedItem="{Binding SelectedTracking, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
RowDetailsTemplate="{StaticResource FTC_TrackingFullDetailTemplate}">
</DataGrid>
I have a comboBox in one column that is bound to an "idAction" property of the SelectedTracking object. When the user changes the selection of this comboBox, I want to assign the values of two other combo boxes in two other columns of the datagrid. These other columns are not bound to properties of the view model, rather they are bound directly to the properties of the SelectedTracking object. These properties of the SelectedTracking object are iSource_Type and iDestination_Type.
Here is the column definition for iSourceType:
<DataGridTemplateColumn Header="SOURCE" SortMemberPath="tracking_source.chrSource" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Style="{StaticResource FTC_DetailComboBox}" Margin="0" Padding="3"
ItemsSource="{Binding DataContext.TrackingSources, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
SelectedValuePath="idSource"
DisplayMemberPath="chrSource"
SelectedValue="{Binding iSource_Type, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
So when I assign these (iSource_Type, iDestination_Type) values in the ViewModel code (in a selectionChanged function of the first "Action" comboBox) the values are updated on the object itself. But the change is not reflected back to the UI's comboboxes bound to these properties.
What I tried:
First:
I have an implementation of INotifyPropertyCHanged with a function called RaisePropertyChanged. THis is provided through the MVVM_Light framework. SO i tried to use the following:
RaisePropertyChanged("iDestination_Type")
RaisePropertyChanged("iSource_Type")
RaisePropertyChanged("SelectedTracking")
RaisePropertyChanged("SelectedTracking.iDestination_Type")
RaisePropertyChanged("SelectedTracking.iSource_Type")
But these do not work.
Second:
I also tried to create properties in the viewmodel that bound to the SelectedTracking object. But this just caused all the tracking objects to get the same values.
Question:
Can INotifyPropertyChanged work on properties that are not a part of the viewmodel, but are properties of objects found in the view model. If so, what syntax do I need in the INotifyPropertyChanged event?
Additional INformation:
The MVVM-Light implementation of INotifyPropertyChanged (RaisePropertyChanged()) does not accept an empty string that would normaly update all UI elements. So is there a way I can override the Implementation of INotifyPropertyCHanged in just one CLass?
If I understand your problem correctly you would like a way to notify your ViewModel of changes to your Model.
If so you can implement INotifyPropertyChanged in your model and subscribe to the model objects PropertyChanged event in your ViewModel. Here you can raise the property changed notification on your ViewModel properties.
A simple example to demonstrate the concept:
Model:
public class Tracking : INotifyPropertyChanged
{
private string _isourcetype;
private string _idestinationtype;
public string SourceType
{
get { return _isourcetype; }
set
{
_isourcetype = value;
OnPropertyChanged("SourceType");
}
}
public string DestinationType
{
get { return _idestinationtype; }
set
{
_idestinationtype = value;
OnPropertyChanged("DestinationType");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
ViewModel:
public class TrackingViewModel : ViewModelBase
{
private Tracking _selectedTracking;
public string DestinationType
{
get { return _selectedTracking.DestinationType; }
}
public string SourceType
{
get { return _selectedTracking.SourceType; }
}
public Tracking SelectedTracking
{
get { return _selectedTracking; }
set
{
_selectedTracking = value;
RaisePropertyChanged("SelectedTracking");
}
}
public TrackingViewModel()
{
_selectedTracking = new Tracking();
_selectedTracking.PropertyChanged += SelectedTracking_PropertyChanged;
}
void SelectedTracking_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "SourceType":
RaisePropertyChanged("SourceType");
break;
case "DestinationType":
RaisePropertyChanged("DestinationType");
break;
}
}
}

Binding data to combobox in WPF

I am a beginner in programming, especially WPF. I have an application in WPF. I have changed connection to .sdf database from Entity Framework to SqlCeCommand. Unfortunatelly, before this I had the following code for binding a ComboBox.
<DockPanel Grid.Row="4">
<Button x:Name="LoadButton" Height="20" ToolTip="Choose setting name to load" Width="75" Padding="2,2,2,2" Margin="2,0,2,0" HorizontalAlignment="Left" VerticalAlignment="Center" Content="Load Settings" Command="{Binding LoadSettingsCommand}"/>
<ComboBox x:Name="LoadSettingsComboBox" ToolTip="Choose setting name to load" ItemsSource="{Binding Mode=OneWay, Path=Settings/}" SelectedValue="{Binding LoadSettingName, Mode=OneWayToSource}" SelectedValuePath="Name" Grid.Column="1" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock HorizontalAlignment="Center" Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DockPanel>
and:
List<Setting> _settings;
Settings = new CollectionView(_settings);
And it worked. After changing connection to DataBase there is no error, but ComboBox doesn't show any data. Before Setting class was generated by entity framework. Now, I made my own class Setting. What this class should implement? Can you help me?
Vote this answer if you find it helpful.
As per my understanding if you are using Setting as data object you need to store it into ObservableCollection<>. Use like this :
private ObservableCollection<Settings> _settingList = new ObservableCollection<Settings>();
public ObservableCollection<Settings> SettingList
{
get
{
return this._settingList;
}
set
{
if(value==null)
return;
this._settingList = value;
//OnPropertyChanged(()=>this.SettingList); //It is not required as ObservableCollection<> itself notifies on collection changed.
}
}
If you are implementing your own Setting class then you should implement INotifyPropertyChanged interface to bind properties with combobox item. Below is the code for your reference:
public class SettingsModel : INotifyPropertyChanged
{
#region INotifyPropertyChanded event Implementation
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged<TProperty>(Expression<Func<TProperty>> propertyExpression)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null == handler)
return;
if (null != propertyExpression)
{
var e = new PropertyChangedEventArgs(((MemberExpression)propertyExpression.Body).Member.Name);
handler(this, e);
}
}
public void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != PropertyChanged)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Above implementation gives you a method OnPropertyChanged(string propertyName). You need to call this method on "set" section of your each property in your class. Whenever property value gets changed it will notify to the DependencyProperty of control.
Hope this will help you.

TextBox, Button and ListBox in a ListBox

I have a listbox with a bunch of contols in each list item.
<ListBox x:Name="projectList" IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" />
<ListBox x:Name="taskList" ItemsSource="{Binding Tasks}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox x:Name="textBoxTask" />
<Button
x:Name="ButtonAddNewTask"
Content="Test"
CommandParameter="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext}"
Click="ButtonAddNewTask_Click"
/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
When I click on the button in the listbox i want to add a new item to the listbox within the listbox. I've come this far. So my question is how do I get hold of the textbox and how do I update the listbox?
Here is my click event
private void ButtonAddNewTask_Click(object sender, RoutedEventArgs e)
{
Button button = (Button)sender;
Project proj = button.DataContext as Project;
if(proj.Tasks == null)
proj.Tasks = new List<Task>();
proj.Tasks.Add(new Task("Added Task"));
}
Thanx
The easiest solution would likely be to have one object represent each item in the outer ListBox. It would then have properties that would represent each control in the item - the text in the TextBox, and the items in the ListBox (a list of Tasks, I think, based on your Click handler).
In your Click handler, you can get the Button's DataContext (which should be an item in the collection of the outer list), and add a new Task to that object's list of tasks. Since the inner ListBox is bound to that list, it should be updated with the new item (assuming that it sends events when items are added, such as with ObservableCollection).
Update: Based on your comments, the following should work.
Your Project class should have two properties:
class Project
{
public string Name { get; set; }
private ObservableCollection<Task> tasks =
new ObservableCollection<Task>();
public IList<Task> Tasks
{
get { return this.tasks; }
}
}
The Task class just has one property - the name of the task.
The ProjectView class is a wrapper around the Project class (I got this idea from #timothymcgrath's answer). It keeps track of the name of a new task, and the current Project:
class ProjectView : INotifyPropertyChanged
{
public Project Project { get; set; }
private string newTaskName = string.Empty;
public string NewTaskName
{
get { return this.newTaskName; }
set
{
this.newTaskName = value;
this.OnPropertyChanged("NewTaskName");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
PropertyChangedEventHandler eh = this.PropertyChanged;
if(null != eh)
{
eh(this, new PropertyChangedEventArgs(propName));
}
}
}
You'll need a new class that will be used as the DataContext. Something like this:
class Model
{
private ObservableCollection<ProjectView> projects =
new ObservableCollection<ProjectView>();
public IList<ProjectView> Projects
{
get { return this.projects; }
}
}
In the code behind, set the DataContext of the object to an instance of the above class:
public class Window1
{
public Window1()
{
this.InitializeComponent();
this.DataContext = this.model;
}
private Model model = new Model();
}
In the XAML, the bindings should be modified to bind to the above properties:
<ListBox x:Name="projectList" IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Projects}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Project.Name}" />
<ListBox x:Name="taskList"
ItemsSource="{Binding Project.Tasks}"
DisplayMemberPath="Name" />
<TextBox x:Name="textBoxTask"
Text="{Binding Path=NewTaskName, UpdateSourceTrigger=PropertyChanged}"/>
<Button x:Name="ButtonAddNewTask" Content="Test"
Click="ButtonAddNewTask_Click" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Finally, in the click handler for the button, create the task. The DataContext of the Button will be the ProjectView for that item.
private void ButtonAddNewTask_Click(object sender, RoutedEventArgs e)
{
Button btn = (Button)sender;
ProjectView curProject = btn.DataContext as Project;
if(null != curProject)
{
curProject.Project.Tasks.Add(new Task()
{
Name = curProject.NewTaskName
});
}
}
Since all of the controls get their values via binding, you don't need to access the control itself to get the data - just use the data structures that are supplying the controls already.
It would probably be better to move the code that creates the Task into another class (possibly Project), but I just left it in the event handler for ease of typing on my part.
Update 2: Modified the above code to move the NewTaskName property into a separate class that wraps an instance of Project for use with the UI. Does this work better for you?
I'm assuming your Project ListBox is populated with an Collection of Project objects. I would add an AddNewTask ICommand to the Project class and expose it through a property. Then bind the Add New Task button to the new AddNewTask ICommand. For the CommandParameter, put the TaskName in and it will be passed into the command.
Try reading up on some MVVM (Model View ViewModel) for some examples of how this works. It is very clean and works great.
This solution worked for the task at hand so to speak.
private void ButtonAddNewTask_Click(object sender, RoutedEventArgs e)
{
Button button = (Button)sender;
DependencyObject obj = LogicalTreeHelper.GetParent(button);
StackPanel item = obj as StackPanel;
TextBox textBox = item.FindName("textBoxTask") as TextBox;
ListBox listBox = item.FindName("taskList") as ListBox;
Project proj = button.DataContext as Project;
if(proj.Tasks == null)
proj.Tasks = new List<Task>();
listBox.ItemsSource = proj.Tasks;
listBox.Items.Refresh();
}

Resources