I have an issue whereby when I update the text on a Comboboxitem it isnt instantly reflected on the UI. One has to click the Combobox to show the items (which have the correct text). Any ideas why? Note this exact code works perfectly in WPF
The property that defines the string to show
public string NormallyOpenString
{
get
{
if (this.IsInput)
{
return "High";
}
else if (this.IsRelay)
{
return "Open";
}
else
{
return "Open (High)";
}
}
}
Which is bound to a Combobox like so
<ComboBox SelectedIndex="{Binding Normally, Mode=TwoWay}" >
<ComboBoxItem Content="{Binding NormallyOpenString}" />
<ComboBoxItem Content="{Binding NormallyClosedString}" />
</ComboBox>
When another combobox changes I want to update the text as it changes what IsInput / IsRelay is. I do this via NotifyPropertyChanged like so
this.NotifyPropertyChanged("NormallyOpenString");
this.NotifyPropertyChanged("NormallyClosedOpenString");
this.NotifyPropertyChanged("Normally");
I've never done it that way, so I can't vouch for it. This is how I do property change notification:
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
private string normallyOpenString = "I'm an open string!";
public string NormallyOpenString
{
get { return normallyOpenString; }
set
{
normallyOpenString = value;
RaisePropertyChanged("NormallyOpenString");
}
}
}
So now, whenever anyone calls your setter, anything bound to your property will be updated. So if it gets set from one binding, all other bindings that are also bound to it will be updated.
I think you should rather use the SelectedItem property.
Related
I have multiple UserControl which contain a shared ViewModel.
It's a DataGrid where the user click on a row to see the detail of the row (the actual structure is more complex).
The problem is when I handle the SelectionChanged in the grid, I update the shared ViewModel to update the ContactDetail but it doesn't update the value in the TextBoxes (the object is updated in ContactDetail but values are not displayed).
ListContact.xaml.cs
public void contactsTable_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
contacts.current_identity = //Get the associated `IdentityViewModel`
}
ContactDetail.xaml.cs
public partial class ContactDetail : UserControl
{
public ContactsViewModel contacts;
public DetailContact(ContactsViewModel contacts)
{
InitializeComponent();
this.contacts = contacts;
this.DataContext = contacts;
}
}
ContactDetail.xaml
<UserControl x:Class="ContactDetail">
<TextBox Name='address' Text="{Binding Path=contacts.current_identity.address, Mode=TwoWay}"/>
<TextBox Name='phone' Text="{Binding Path=contacts.current_identity.phone, Mode=TwoWay}"/>
<TextBox Name='email' Text="{Binding Path=contacts.current_identity.email, Mode=TwoWay}"/>
</UserControl>
ContactsViewModel.cs (IdentityViewModel uses the same structure)
public class ContactsViewModel : INotifyPropertyChanged
{
private List<Contact> _contacts;
public List<Contact> contacts;
{
get { return _contacts; }
set { _contacts = value; OnPropertyChanged("contacts"); }
}
private IdentityViewModel _current_identity;
public IdentityViewModel current_identity
{
get { return _current_identity; }
set { _current_identity = value; OnPropertyChanged("current_identity"); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
The question is, why doesn't this work and how to notify ContactDetail so that it displays the new value ?
Your data for contacts changes but the original reference location Binding Path=contacts.current_identity.address is still being referred to in the binding. I.E. address is still valid and has not changed. What changed was contacts.current but you are not binding to that.
Remember that binding is simply reflection to a location reference. If the original address changes you would see a change because that is what is being looked for to have a change. But instead the parent instance is what changed.
You need to refactor your bindings to allow for proper update when the current_identity changes.
I have searched and have come to a brick wall. There seems to be plenty of questions and answers on how to do this, but I can't find anything specifically that I can implement(Obviously an issue with my understanding).
I was hoping to be able to update several Datagrids and going off the premise that you should not be naming you controls, I am lost as to how to get this to work.
So what I have been able to come up with thus far is using the System.Windows.Interactivity assembly. But I have no Idea how to implement the code behind (I certainly could show all the code I have tried to get it to work, but unfortunately it would just clutter the post, so I am not including it). I have research as much as I can on implementing ICommand.
So I have the XAML:
<i:Interaction.Triggers>
<i:EventTrigger EventName="RowEditEnding">
<i:InvokeCommandAction Command="{Binding CanExecuteChanged}" />
</i:EventTrigger>
</i:Interaction.Triggers>
But I can't seem to get the code behind to be able to notify when the RowEditEnding has finished and be able to update the database with the new data.
So with the MVVM model in mind, how do I go about getting an event to fire so I can update the database?
EDIT2: Changed People to an ObservableCollection instead of List. Added CollectionChanged event to handle attaching/removing PropertyChanged events from Person objects in the collection.
EDIT: changed foreach in ViewModel for consistency.
First, Model-View-ViewModel(MVVM) means that you should have as little code behind as possible, preferably none in fact. Instead, logic for the UI should be done in xaml (the View), while logic for the data should be done in the Model and the organization of the data into presentable form should be done via the View Model, which is a separate file that knows nothing about WPF or the UI.
It also seems you have a misunderstanding as to how data binding works. You are referring to stuff you have done in code behind, but binding expressions usually point to a property on your view's DataContext, which in MVVM should be set to your ViewModel. There is a good tutorial here that can get you started with binding. I also recommend the followup posts to that one, they helped me a great deal when I was starting out WPF.
Now for the situation with the DataGrid. First, here is a good tutorial on the WPF DataGrid.
Next, you state that you want to update the database when the row has been updated. Here is an example of how to do that in MVVM style:
Suppose you have a view with a DataGrid like this:
<UserControl x:Class="MyProject.MyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid ItemsSource="{Binding People, Mode=OneWay}">
<DataGrid.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding FirstName}"/>
<DataGridTextColumn Header="Last Name" Binding="{Binding LastName}"/>
<DataGridTextColumn Header="Age" Binding="{Binding Age}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</UserControl>
Code behind:
namespace TestWPFApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MyViewModel();
}
}
}
Note that the code behind is almost empty. Just the default code, plus DataContext = new MyViewModel();. As I mentioned earlier, the DataContext of your view should be your View Model.
MyViewModel looks like this:
public class MyViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged Impl
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
private ObservableCollection<Person> m_people;
public ObservableCollection<Person> People
{
get { return m_people; }
private set
{
if (value == m_people)
return;
m_people = value;
OnPropertyChanged();
}
}
public MyViewModel()
{
m_people = new ObservableCollection<Person>();
m_people.CollectionChanged += m_people_CollectionChanged;
m_people.Add(new Person() { FirstName = "Bob", LastName = "Brown", Age = 45 });
m_people.Add(new Person() { FirstName = "Sarah", LastName = "Smith", Age = 25 });
}
private void m_people_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null && e.NewItems.Count > 0)
{
foreach (INotifyPropertyChanged item in e.NewItems.OfType<INotifyPropertyChanged>())
{
item.PropertyChanged += people_PropertyChanged;
}
}
if (e.OldItems != null && e.OldItems.Count > 0)
{
foreach (INotifyPropertyChanged item in e.OldItems.OfType<INotifyPropertyChanged>())
{
item.PropertyChanged -= people_PropertyChanged;
}
}
}
//Property Changed will be called whenever a property of one of the 'Person'
//objects is changed.
private void person_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var row = sender as Person;
SaveData(row);
}
private void SaveData(Person row)
{
//Save the row to the database here.
}
}
I have a property of type List<Person> in my view model. Person looks like this:
public class Person : INotifyPropertyChanged
{
#region INotifyPropertyChanged Impl
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
private string m_firstName;
public string FirstName
{
get { return m_firstName; }
set
{
if (value == m_firstName)
return;
m_firstName = value;
OnPropertyChanged();
}
}
private string m_lastName;
public string LastName
{
get { return m_lastName; }
set
{
if (value == m_lastName)
return;
m_lastName = value;
OnPropertyChanged();
}
}
private int m_age;
public int Age
{
get { return m_age; }
set
{
if (value == m_age)
return;
m_age = value;
OnPropertyChanged();
}
}
}
The important thing to note here is INotifyPropertyChanged, this interface is very important to MVVM and WPF data binding in general. When implemented properly, it causes an object to publish a PropertyChanged event whenever one of its properties is changed. This tells any bound WPF controls that they should get the new value, and also allows your ViewModel to watch them for changes. So in the view model, we attach an event handler to the CollectionChanged event on People, which then takes care of attaching the PropertyChanged event handler for each item added to the collection.
The CollectionChanged event will be called whenever an item is added, removed, or replaced in the collection and will remove the PropertyChanged handler from the old items and add the handler to the new ones. It is important to remember to remove the handlers, or items removed from the collection may not be properly garbage collected.
The person_PropertyChanged method will be called each time a property of one of the Person objects changes. In person_PropertyChanged we then call the methods to update the database, passing the updated row (Person in this case), like below:
//Property Changed will be called whenever a property of one of the 'Person'
//objects is changed.
private void person_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var row = sender as Person;
SaveData(row);
}
private void SaveData(Person row)
{
//Save the row to the database here.
}
Each row in the grid I displayed above represents one person object. Whenever the user changes a value of one of the cells in the grid, the corresponding property of the Person object that the row represents will be updated as well, which will trigger a PropertyChanged event, and call person_PropertyChanged.
Suppose the user changes the Age column of "Bob" to 37. After the user hits enter or moves off the cell for Age the Age property of the Person object representing "Bob" will be changed to 37 from 45. This will cause that Person object to raise PropertyChanged, which will call the person_PropertyChanged method in MyViewModel. person_PropertyChanged will then call SaveData which is where you would put the code to save the updated Person row to the database.
If you have any questions, let me know!
Basically, I am reading a value from an xml file and displaying that value in a text box. This text box is editable so the user can make changes to it and when the session ends that value gets stored back in the xml file.
I know that the values correctly get stored and loaded to the text box. But I'm baffled as to why I cannot see the bound value that should be displayed in the text box..
This is the text box:
<UserControl x:Class="test.myView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ui="clr-namespace:test.myView"
xmlns:local="test.myControls;assembly=test.mycontrols">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Style.xaml"/>
</ResourceDictionary>
</UserControl.Resources>
<StackPanel Margin="8">
<TextBlock Text="Starting URL"/>
<TextBox Margin="0,5" FontSize="12" Height="30" Width="360" Text="{Binding myValue, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Style="{StaticResource WaterMarkTextBox}" local:WaterMarkTextHelper.WatermarkText="ENTER Value" />
</StackPanel>
This is the INotifyPropertyChanged:
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaiseChangeNotification(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
This is the property im binding to:
public string myValue
{
get { return _settings.myValue; }
set { _settings.myValue= value; }
}
I think it maybe because the value of the text box to empty before the code reaches the part where the start url is set using data binding. Does there exist a validate command in wpf that forces it to view the most up to date value?
There is not enough information to know the exact problem, but there are a few things to check when this sort of things happen.
Check your Output window in Visual Studio. This will give you any binding errors. This will help solve some of the following problems:
Not having your property set to public
Typo with your property name
Not having the DataContext set
Make sure your DataContext implements the INotifyPropertyChanged interface. This is required by the implementation of WPF to update the binding. You would do this like the following:
public class YourDataContext : INotifyPropertyChanged
{
private object _myvalue;
public object myvalue
{
get
{
return _myvalue;
}
set
{
if (_myvalue == value)
return;
_myvalue = value;
OnPropertyChanged("myvalue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
You can also look up several strategies to simplify this, like INotifyPropertyChanged weaving or a BaseViewModel class.
Edit
If myvalue is in your ViewModel that inherits from your ViewModelBase then all you need to do is change the implementation of your property to:
public string myValue
{
get
{
return _settings.myValue;
}
set
{
if (_settings.myValue == value)
return;
_settings.myValue = value;
RaiseChangeNotification("myValue");
}
}
Try to add one dummy converter and see if binding is getting fired when you change value from UI textbox (Convert back method needs to be executed). Else take help of snoop (http://snoopwpf.codeplex.com/) This will help you check status of binding.
And add RaiseNotification in setter if you want to update data from source to target.
public string myValue
{
get { return _settings.myValue; }
set { _settings.myValue= value; RaiseChangeNotification("myValue") }
}
I've copied code from the blank panorama project and made some adjustments, but somewhere something ain't right.
I've got my textblock set up:
<TextBlock Grid.Column="0" Grid.Row="0" Text="{Binding ElementName=CurrentPlaceNow, Path=Temperature}" />
My model looks like this:
public class CurrentPlaceNowModel : INotifyPropertyChanged
{
#region PropertyChanged()
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
private string _temperature;
public string Temperature
{
get
{
return _temperature;
}
set
{
if (value != _temperature)
{
_temperature = value;
NotifyPropertyChanged("Temperature");
}
}
}
}
And defined defined in the MainViewModel():
public CurrentPlaceNowModel CurrentPlaceNow = new CurrentPlaceNowModel();
Finally I've added a modifier to a buttonclick:
App.ViewModel.CurrentPlaceNow.Temperature = "foo";
Now, why isn't anything showing up in the textbox?
Your Binding should navigate through the ViewModel. Binding to an ElementName tries to look at another object in the Visual Tree.
Change your Binding to this:
<TextBlock
Grid.Column="0"
Grid.Row="0"
Text="{Binding CurrentPlaceNow.Temperature}" />
Verify your ViewModel's property is formatted properly:
private CurrentPlaceNowModel _CurrentPlaceNow = new CurrentPlaceNowModel();
public CurrentPlaceNowModel CurrentPlaceNow
{
get { return _CurrentPlaceNow; }
set
{
_CurrentPlaceNow = value;
NotifyPropertyChanged("CurrentPlaceNow");
}
}
As long as your View's DataContext is your MainViewModel, you are good to go.
You are using ElementName wrong. ElementName is when you want to bind to another XAML control, not to (view)model.
To bind to model, set instance of that model to DataContext property and bind only Path.
I got a sample mvvm app. The UI has a textbox, a button and a combobox. when I enter something in the textbox and hit the button, the text I enter gets added to an observablecollection. The Combobox is bound to that collection. How do I get the combobox to display the newly added string automaticly?
As I understand correctly, you want to add an item and select it.
Here is the example how it can be done using ViewModel and bindings.
Xaml:
<StackPanel>
<TextBox Text="{Binding ItemToAdd}"/>
<ComboBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" />
<Button Content="Add" Click="Button_Click"/>
</StackPanel>
ViewModel:
public class MainViewModel:INotifyPropertyChanged
{
public ObservableCollection<string> Items { get; set; }
public string ItemToAdd { get; set; }
private string selectedItem;
public string SelectedItem
{
get { return selectedItem; }
set
{
selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
public void AddNewItem()
{
this.Items.Add(this.ItemToAdd);
this.SelectedItem = this.ItemToAdd;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The MainViewModel has 3 properties (one for the TextBox and two other for the ComboBox) and the method AddNewItem without parameters.
The method can be triggered from a command, but there is no standard class for commands, so I will call it from the code-behind:
((MainViewModel)this.DataContext).AddNewItem();
So you must explicitly set an added item as selected after you add it to a collection.
Because the method OnItemsChanged of the ComboBox class is protected and can't be used.
If the ComboBox is bound to an ObservableCollection, the ComboBox will be updated as soon as the collection is changed.
That's the advantage of using an ObservableCollection - you don't need to do any extra coding to update the UI.
If this is not the behavior you're seeing, perhaps you can post some code/xaml.