I have a gridview shown as below in XAML
<ListView x:Name="listTasks">
<ListView.View>
<GridView x:Name="gridTasks">
<GridViewColumn Header="ID" HeaderStringFormat="Lowercase" Width ="26" DisplayMemberBinding="{Binding id}"/>
<GridViewColumn Header="Something" Width="113" DisplayMemberBinding="{Binding something}"/>
<GridViewColumn Header="State" Width="179" DisplayMemberBinding="{Binding currentState}"/>
</GridView>
</ListView.View>
</ListView>
and i have a button which adds to this gridview using the below
m.myList.Add(new mylistview.myitems
{
id = m.id,
something= m.something,
currentState = m.currentState,
});
This button works perfectly by adding the row into the gridview. However I would like to modify theCurrentState using a method that is running. How would I locate for example, ID = "8" and then modify theCurrentState for that row?
UPDATED CODE SHOWN
I've now replaced my list<Task> with ObservableCollection and managed to get it to add to my listview when I click onto my button. However, I am struggling to implement the iNotifyPropertyChanged into my code and getting it to work correctly... Below is my listview class
public class mylistview : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string _currentState;
public string currentState
{
get { return _currentState; }
set
{
_currentState = value;
OnPropertyChanged();
}
}
public ObservableCollection<myitems> _myList = new ObservableCollection<myitems>();
public ObservableCollection<myitems> myList
{
get { return _myList; }
}
private static int _id = 0;
public class myitems
{
public int id { get; set; }
public string something{ get; set; }
public string currentState { get; set; }
}
public int id
{
get { return _id; }
set { _id = value; }
}
}
So I see you're using data bindings already, that's good. But your question makes me think you haven't quite grasped everything it can do for you yet.
My recommendation would be to forget about adding items directly to listOfTasks.Items. Instead you should make an ObservableCollection to hold that list and bind the listOfTasks to it. Like so:
ObservableCollection tasks = new ObservableCollection<mylistview.myitems>();
ListOfTasks.ItemsSource = tasks;
With that binding in place you should be able to simply add new items to the tasks list when they click your button:
tasks.Add(new mylistview.myitems
{
id = theId,
something= something,
currentState = theCurrentState,
});
and it should automatically update the GUI.
The last step is to make sure that the class mylistview.myitems implements INotifyPropertyChanged. This is easier than it sounds; you just need to have it trigger an event any time the property is set. Something like so:
public class exampleProperties: INotifyPropertyChanged
{
//this is the event you have to emit
public event PropertyChangedEventHandler PropertyChanged;
//This is a convenience function to trigger the event.
//The CallerMemberName part will automatically figure out
//the name of the property you called from if propertyName == ""
protected void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
}
//Any time this property is set it will trigger the event
private string _currentState = "";
public string currentState
{
get { return _currentState; }
set
{
if (_currentState != value)
{
_currentState = value;
OnPropertyChanged();
}
}
}
}
Now that the gridview is bound to an ObservableCollection and the items held in that collection can notify interested GUI controls that their properties have changed, you should simply be able to update the GUI simply by changing the appropriate item in the collection.
And here's an example of a form that uses the whole technique: https://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged(v=vs.110).asp
edit
I forgot that you specifically need to bind to the ItemSource property of the ListView. The way I have done it in the past is to set ItemsSource={binding} in the ListView's xaml and then assign an ObservableCollection to ListView.DataContext. However I have found an easier way and updated the original post with it. Here's a reference: http://www.wpf-tutorial.com/listview-control/listview-with-gridview/
Edit 2
Aha, you're adding the iPropertyChangedNotify to the wrong thing. It goes on the myitems class like so:
public class myitems : iNotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private int _id;
public int id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged();
}
}
public string something{ get; set; }
public string currentState { get; set; }
}
I leave updating the current state and something properties as an excersize. They also need to trigger the OnPropertyChanged event when their value is set.
Maybe with
listOfTasks.Items.Cast<ListViewItem>().First(item => item.ID == "8").theCurrentState = newState;
//I'm not sure about the Cast stuff, because I don't know what types the ListView uses for its items
Of course you could iterate through the items with a loop and check manually for the ID as well.
Related
I'm using a CheckListBox from the WPF Toolkit and trying to bind it to my ViewModel. In addition to obtaining the selected values from the control I would also like to be able to reset it through a button click which would clear any selections. I'm stuck on how to bind the selected or checked state of each item in the collection, but if my whole approach is off I would appreciate some direction on that as well.
I've created a simple class with a string descriptor and a Boolean property that I planned to use to indicate the state of each checkbox...
public class DrugInfluence : INotifyPropertyChanged
{
public string Impairment { get; set; }
private bool isChecked;
public bool IsChecked
{
get { return isChecked; }
set
{
if(isChecked != value)
{
isChecked = value;
OnPropertyChanged("IsChecked");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
A collection of DrugInfluence objects in the ViewModel called ImpairmentList that I want to bind to the CheckListBox control iteself...
public List<DrugInfluence> ImpairmentList
{
get
{
return impairmentList;
}
set
{
if(impairmentList != value)
{
impairmentList = value;
NotifyPropertyChanged("ImpairmentList");
}
}
}
And the XAML I'm using to bind the ViewModel to the CheckListBox control...
<sdk:CheckListBox Margin="6"
ItemsSource="{Binding ImpairmentList}"
DisplayMemberPath="Impairment"
SelectedMemberPath="IsChecked"
SelectedItemsOverride="{Binding SelectedImpairments, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</sdk:CheckListBox>
Is this the control you are using? If so, it looks like SelectedMemberPath is the property you want to bind to. You will also need to raise an event when the IsChecked property changes if you want to check/uncheck programatically. For instance,
public class DrugInfluence : INotifyPropertyChanged
{
public string Impairment { get; set; }
private bool _isChecked;
public bool IsChecked
{
get{ return _isChecked;}
set
{
if (_isChecked!= value)
{
_isChecked= value;
OnPropertyChanged("IsChecked");
}
};
}
}
And then implement the rest of the INotifyPropertyChanged members
I have an Observable collection, but after updating the collecion, my Listview is not updating even after raising Property Changed event see below code:-
Look below XAML:-
<ListView Grid.Row="1" Grid.Column="0" Name="lvGroups" Margin="0,34,0,0"
Grid.RowSpan="2" ItemsSource="{Binding Path=*VideoGroupList*,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
<GridView>
<GridViewColumn Width="150" DisplayMemberBinding="{Binding *Name*}" />
</GridView>
Look below Class
public class VideoGroupViewModel : ObservableEnitiy
{
public ObservableCollection<Group> VideoGroupList { get; set; }
}
public abstract class ObservableEnitiy : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
if (this.PropertyChanged != null)
{
var e = new PropertyChangedEventArgs(propertyName);
this.PropertyChanged(this, e);
}
}
}
[Serializable]
public class Group : PropertyChangedNotification
{
public int ID { get; set; }
[Required(ErrorMessage = "Group name is required.")]
public string *Name*
{
get { return GetValue(() => Name); ; }
set
{
SetValue(() => Name, value);
}
}
[XmlIgnore]
public bool IsSelected { get; set; }
}
protected T GetValue<T>(Expression<Func<T>> propertySelector)
{
string propertyName = GetPropertyName(propertySelector);
return GetValue<T>(propertyName);
}
I am calling this way
VideoGroupList = new ObservableCollection<Group>(videoGroupManager.GetVideoGroups());
OnPropertyChanged("VideoGroupList");
Add the following using statements:
using System.Collections.Generic;
using System.Linq;
Change your VideoGroupList line to:
VideoGroupList.Clear();
videoGroupManager.GetViewGroups().ToList().ForEach(x => VideoGroupList.Add(x));
This better utilises the change notification built into the ObservableCollection
According to the MSDN Documentation, CollectionChanged will be raised on adding, removing and changing an element in an ObservableCollection. I found this not to be the case however.
ObservableCollection.CollectionChanged will only be thrown on add/remove.
I use this FullyObservableCollection here instead, with the modification to have T be my ViewModelBase class instead of INotifyPropertyChanged directly.
When I declare my Property then, I use a snippet with the following output:
private FullyObservableCollection<Group> _videoGroupList;
public FullyObservableCollection<Group> VideoGroupList
{
get { return _videoGroupList; }
set
{
if(_videoGroupList == value) return;
var nil = _videoGroupList == null;
_videoGroupList = value;
// If null before and not after (declaring it after being null),
// call OnPropertyChanged on Change of Collection.
if(nil && _videoGroupList != null)
_videoGroupList.CollectionChanged += (s,e)
=> OnPropertyChanged(nameof(VideoGroupList));
OnPropertyChanged();
}
}
This way you will always have INotifyPropertyChanged Notifications on Add, Remove, Replace and Change action on the collections and every single one of its members. It will save you a lot of hassle since you do not need to reinstanciate the whole collection if one of hundreds of thousands properties in it changes.
Remember to initialize it in your constructor though. :)
I'm trying to fiddle a little with WPF bindings, so I created a simple project.
Here's the code:
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int Age {
get { return age; }
set {
age = value;
FirePropertyChanged("Age");
}
}
public string Name
{
get { return name; }
set
{
name = value;
FirePropertyChanged("Name");
}
}
private void FirePropertyChanged(string v)
{
if(PropertyChanged !=null)
PropertyChanged(this, new PropertyChangedEventArgs(v));
}
private int age;
private string name;
}
My viewmodel contains ObservableCollection of Person, and single Person to track selected Person.
I've bound listbox's ItemsSource to ObservableCollection, and SelectedItem to single Person, called CurrentPerson. Also, I've bound TextBox to CurrentPerson.Name.
Code works fine, but whenever I change content of TextBox - my listbox also changes. And no matter what combination of "OneWay, TwoWay, OneWayToSource" binding modes on listbox\selecteditem I cannot prevent listbox from updating from CurrentPerson.
How can I prevent this behavior? I'd like to update listbox from CurrentPerson only by using ICommand interface from VM.
There is only one copy of the Person object which is being used in both ListBox.ItemsSource and TextBox.Text, so naturally updating that object from one location will reflect the change in the other as well.
Two easy solutions would be
Change the BindingMode on TextBox.Text to Explicit, so it doesn't update the Person object until you tell it to
Use a separate string property for TextBox.Text and copy it over to your SelectedPerson.Name whenever the command executes
Personally I prefer the second option because I'm not a big fan of bindings that don't accurately reflect the data object behind the UI component, and it would allow the user to change the SelectedItem without resetting the TextBox value.
For an example of the second option, your ViewModel might look like this :
public class MyViewModel()
{
ObservableCollection<Person> People { get; set; }
Person SelectedPerson { get; set; }
string NewPersonName { get; set; }
ICommand UpdatePersonName { get; }
}
where the UpdatePersonName command would execute
SelectedPerson.Name = NewPersonName;
and the CanExecute would only return true if
SelectedPerson != null
&& !NewPersonName.IsNullOrWhiteSpace()
&& NewPersonName != SelectedPerson.Name
I'm not sure if I've followed the question properly.
So, we have a class Person as
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int Age
{
get { return age; }
set
{
age = value;
FirePropertyChanged("Age");
}
}
public string Name
{
get { return name; }
set
{
name = value;
FirePropertyChanged("Name");
}
}
private void FirePropertyChanged(string v)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(v));
}
private int age;
private string name;
}
And we have a view model as
public class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<Person> List { get; set; }
Person currentPerson;
public Person CurrentPerson {
get { return currentPerson; }
set { currentPerson = value;
FirePropertyChanged("CurrentPerson");
}
}
private void FirePropertyChanged(string v)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(v));
}
public event PropertyChangedEventHandler PropertyChanged;
}
The xaml is
<ListBox ItemsSource="{Binding List}" SelectedItem="{Binding CurrentPerson}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Name}" Width="100" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And I bind the view model to the view via
ViewModel vm = new ViewModel();
vm.List = new ObservableCollection<Person>();
foreach (var i in Enumerable.Range(1,10))
{
vm.List.Add(new Person() { Name = "Test" + i.ToString(), Age= i });
}
vm.CurrentPerson = null;
this.DataContext = vm;
Whenever I change the value at textbox, it updates the name properly. I tried to add a handler for list changed, but it doesn't happen to get triggered.
vm.List.CollectionChanged += List_CollectionChanged;
void List_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
MessageBox.Show(e.Action.ToString());
}
Can you comment if it isn't the same as your problem statement?
If you want to control when and what is saved/updated, you obviously need is a ViewModel for editing your Person model.
When selecting a person in your Listbox, you have to pass the person's id (avoid passing the object itself) to the PersonEditViewModel which is bound to the properties that shall be edited, load the persons data into the PersonEditViewModel and then edit. Once you hit the "Save" button, it should commit the change and update the database or whatever you are using for persistence.
Use either events/messages to pass values/events back and forth, or use a navigation approach (like INavigationAware interface in Prism).
I am using a poco class for the following screen but im just wondering how I would achieve the move up and down elements of this screen
I am using ObservableCollection to add my items to a mutual list my question is how would I achieve the move up and move down. I no I would need to change the poco class in real time but not sure how I would achieve this
private void AddColumn(object sender, RoutedEventArgs e)
{
if (this.WizardData == null)
return;
if (this.WizardData.ConcreteCustomColumnsProxy == null)
this.WizardData.ConcreteCustomColumnsProxy = new ObservableCollection<CustomColumnsModel>();
this.WizardData.ConcreteCustomColumnsProxy.Add(new CustomColumnsModel() { CustomColumnsDisplayName = txtDsiplayName.Text
, CustomColumnsOrder = 1, CustomColumnsWidth = Convert.ToInt32(txtWdith.Text) });
this.listView1.ItemsSource = this.WizardData.ConcreteCustomColumnsProxy;
this.listView1.UnselectAll();
this.listView1.Items.Refresh();
My Poco class is as follows
public event PropertyChangedEventHandler PropertyChanged;
public const string IdPropertyName = "CustomColumnsID";
private Guid _Id = Guid.Empty;
public Guid CustomColumnsID
{
get { return _Id; }
set
{
if (_Id == value)
return;
_Id = value;
NotifyPropertyChanged(IdPropertyName);
}
}
public string CustomColumnsDisplayName { get; set; }
public int CustomColumnsWidth { get; set; }
public int CustomColumnsOrder { get; set; }
protected void NotifyPropertyChanged(string key)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(key));
}
}
public EnterpriseManagementObject ActualData { get; private set; }
}
You have some sort of DataGrid control. You need to data bind a collection property to the DataGrid.ItemsSource property and a property of the same type as the items in the collection to the DataGrid.SelectedItems property:
<DataGrid ItemsSource="{Binding YourCollectionProperty}"
SelectedItem="{Binding YourItemProperty}" />
With the DataGrid.SelectedItems property data bound to your YourItemProperty, you can set which item is selected in the UI by setting this property. So to move the selected item down one position, you could do something like this:
int selectedIndex = YourCollectionProperty.IndexOf(YourItemProperty);
if (YourCollectionProperty.Count > selectedIndex)
YourItemProperty = YourCollectionProperty.ElementAt(selectedIndex + 1);
So that is how you perform the actions of the 'Move Down' Button and the 'Move Up' Button would work similarly. Then all you would need to do is to hook up some Click or ICommand event handlers.
Below is an example of my model , ViewModel and xaml binding. The viewmodel implements INotifyPropertChanged. The problem i'm having is...when the wpf form first loads i set ActiveStock and i see both setter and getter being called and the ui is updated to reflect the data correctly.
However, when i later set StockViewModel.ActiveStock, FirePropertyChanged is invoked but i don't see the getter being called, and consequently the UI does not update to reflect the new data. Any ideas what might be happening here?
The second question i have is whether i also need to raise PropertyChanged for the child properties (PriceData and CompanyData) of my model when ViewModel.ActiveStock is changed?
public class Stock
{
public string Ticker { get; set; }
public StockData PriceData { get; set; }
public StockData CompanyData { get; set; }
}
public class StockData
{
...
}
public class StockViewModel:INotifyPropertyChanged
{
private Stock _activeStock;
public Stock ActiveStock
{
get{ return _activeStock;}
set{ _activeStock = value; FirePropertyChanged("ActiveStock");}
}
...
}
XAML:
<UserControl Template="{StaticResource StockTemplate}" DataContext="{Binding ActiveStock}" Tag="{Binding PriceData}" />
<UserControl Template="{StaticResource StockTemplate}" DataContext="{Binding ActiveStock}" Tag="{Binding CompanyData}" />
Edit:
if i remove the DataContext binding for the UserControl and instead set the DataContext for these two controls in code behind when ActiveStock changes, it works fine. why???
The getter is not being called because as far as I can see nothing is "getting" the value, The only properties used are PriceData and CompanyData and these don't use INotifyPropertyChanged
You will have to implement INotifyPropertyChanged on your Stock class for the UI to reflect the changes.
public class Stock : INotifyPropertyChanged
{
private string _ticker;
private StockData _priceData;
private StockData _companyData;
public string Ticker
{
get { return _ticker; }
set { _ticker = value; NotifyPropertyChanged("Ticker"); }
}
public StockData PriceData
{
get { return _priceData; }
set { _priceData = value; NotifyPropertyChanged("PriceData"); }
}
public StockData CompanyData
{
get { return _companyData; }
set { _companyData = value; NotifyPropertyChanged("CompanyData"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
You might want to try to specify the mode property on your datacontext bindings.
DataContext="{Binding ActiveStock, Mode=OneWay}"
I'm not sure that OneTime is the default binding for DataContext, but it would explain so if the above helps.
The second question has been answered by sa_ddam213.
HTH