I've a problem with DataGrid in Prism MVVM.
When I edited entity in other window, then I create new window with DataGrid is not updated. Only run application again help. It's my code:
<DataGrid Name="ClientsTable" IsReadOnly="True" ItemsSource="{Binding Path=ListOfClients, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" AutoGeneratingColumn="DataGrid_AutoGeneratingColumn" Margin="22,10,22,55" Width="800"/>
Part of ViewModel for this window:
public ListOfClientsViewModel(IClientService clientService, IEventAggregator eventAggregator)
{
this.clientService = clientService;
this.eventAggregator = eventAggregator;
ListOfClients.AddRange(clientService.GetAllClientsForList());
}
private ObservableCollection<ClientForList> listOfClients = new ObservableCollection<ClientForList>();
public ObservableCollection<ClientForList> ListOfClients
{
get { return listOfClients; }
set { SetProperty(ref listOfClients, value); }
}
And part of model from collection. It's in other project.
public class ClientForList : INotifyPropertyChanged
{
private string id;
private string name;
private string firstname;
private string lastname;
private string city;
private DateTime createdDate;
[DisplayName("Numer klienta")]
public string Id
{
get { return id; }
set
{
if (value != id)
{
id = value;
OnPropertyChanged();
}
}
}
[DisplayName("Nazwa")]
public string Name
{
get { return name; }
set
{
if (value != name)
{
name = value;
OnPropertyChanged();
}
}
}
[DisplayName("ImiÄ™")]
public string Firstname
{
get { return firstname; }
set
{
if (value != firstname)
{
firstname = value;
OnPropertyChanged();
}
}
}
[.....]
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And, when I edit entity in other window, then I open window with DataGrid by:
void ExecuteListOfClients()
{
ListOfClients listOfClientsWindow = new ListOfClients();
listOfClientsWindow.DataContext = new ListOfClientsViewModel(IClientService, EventAggregator);
listOfClientsWindow.ShowDialog();
}
And data in DataGrid is old. When I restart application data is actual. Help!
Instead of
set { SetProperty(ref listOfClients, value); }
do
set { SetProperty(listOfClients, value); }
and instead of
ListOfClients.AddRange(clientService.GetAllClientsForList());
do
ListOfClients = clientService.GetAllClientsForList().ToObservableCollection();
You may need to implement the extension ToObservableCollection().
Another solution is to change the property ListOfClients as a normal IList and then implment INotifyPropertyChanged inside your view model
Ok, I solved part of the problem.
I have cut the window creation without the manual creation of the ViewModel on:
void ExecuteListOfClients()
{
ListOfClients listOfClientsWindow = new ListOfClients();
listOfClientsWindow.ShowDialog();
}
And in xaml ListOfClients.xaml I changed to:
prism:ViewModelLocator.AutoWireViewModel="True"
And now, after reopening the window, it shows refreshed data;)
But there is still a problem. I've done the LoadData method, which is done in the constructor and assigns data to the ObservableCollection. It works. I have set a button for it and after editing the client I click to load new data and unfortunately nothing happens. In the future I want this method to be called from the EventAggregator after editing the client, but for now I have made a button to test and unfortunately it does not work.
It works. The problem was not in the WPF itself, nor was the view strangely enough. The problem lay in EntityFramework. I had to modify the command choosing data from the database to the collection:
var clients = db.Clients.AsNoTracking().ToList();
Related
I have a array property, in which I want to notify whenever any elements of that array gets changed.
private double[] _OffsetAngles = new double[3];
public double[] OffsetAngles
{
get { return _OffsetAngles; }
set
{
_OffsetAngles = value;
NotifyPropertyChanged();
}
}
if any of the elements of OffsetAngles gets changed, I want to get the notification.
i.e. if I set OffsetAngles[1] = 20; //Trigger should happen.
if I set OffsetAngles[0] = 40; //Trigger should happen again.
Imagine that you are not using double but some class. And then that the filed of that class has changed. Should the array raise property changed? Surely not. So there are multiple solutions that you may consider:
use ObservableCollection and its SetItem method
use ObservableCollection and instead of assigning value remove and insert the value
instead of double use some class that implements INotifyPropertyChanged and when the dobule changes raise this event, it should be right approach if the purpose is data binding
recreate the Array each time (cumbersome and inefficient but still works)
So as others have mentioned, in your case you fire the NotifyPropertyChanged() when the array itself is changed, not any element of the array.
If you want the elements to be able to fire the event you would have to implement a class like:
public class NotifyingData<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private T _Data;
public T Data
{
get { return _Data; }
set { _Data = value; NotifyPropertyChanged(); }
}
}
and then populate your array with that class:
_OffsetAngles[0] = new NotifyingData<double> { Data = 10 };
I don't have access to VS right now, so there might be some errors, but this should be the right concept for you.
This example shows how to create and bind to a collection that derives from the ObservableCollection class, which is a collection class that provides notifications when items get added or removed.
public class NameList : ObservableCollection<PersonName>
{
public NameList() : base()
{
Add(new PersonName("Willa", "Cather"));
Add(new PersonName("Isak", "Dinesen"));
Add(new PersonName("Victor", "Hugo"));
Add(new PersonName("Jules", "Verne"));
}
}
public class PersonName
{
private string firstName;
private string lastName;
public PersonName(string first, string last)
{
this.firstName = first;
this.lastName = last;
}
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
}
The objects in your collection must satisfy the requirements described in the Binding Sources Overview. In particular, if you are using OneWay or TwoWay (for example, you want your UI to update when the source properties change dynamically), you must implement a suitable property changed notification mechanism such as the INotifyPropertyChanged interface.
Ref: https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-create-and-bind-to-an-observablecollection
I had the same issue a while ago. I had to update a DataTable whenever the data changed and that's how I solved it on my program :
public ObservableCollection<KeyStroke> keyList = new ObservableCollection<KeyStroke>();
public class KeyStroke : INotifyPropertyChanged
{
// KeyStroke class storing data about each key and how many types it received
private int id;
private int numPress;
public KeyStroke(int id, int numPress)
{
Id = id;
NumPress = numPress;
}
public int Id
{
get => id;
set
{
id = value;
NotifyPropertyChanged("Id");
}
}
public int NumPress
{
get { return this.numPress; }
set
{
this.numPress = value;
NotifyPropertyChanged("NumPress");
}
}
public event PropertyChangedEventHandler PropertyChanged; //This handle the propertyChanged
private void NotifyPropertyChanged(String propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); //This is the WPF code for the DataGrid but you can replace it by whatever you need
}
}
This should help you. You can also put conditions inside the getter/setters of the properties but I think it's not really pretty
Why can databinding be seen working in the designer:
Click to show image: Databinding seems OK
But runtime shows nothing?
Click to show image: No Data, no usercontrol?
Outline code structure:
ViewModelBase : baseclass inheriting from INotofyPropertychanged
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(storage, value))
return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
}
SiteViewModel : Model class with Id/Name/Description Properties
public class SiteViewModel : ViewModelBase
{
private int _SiteID;
private string _Name;
private string _Description;
public int SiteID
{
get { return _SiteID; }
set { SetProperty(ref _SiteID, value); }
}
public string Name
{
get { return _Name; }
set { SetProperty(ref _Name, value); }
}
public string Description
{
get { return _Description; }
set { SetProperty(ref _Description, value); }
}
}
SitesViewModel: ObservableCollection of SiteViewModel
public class SitesViewModel : ViewModelBase
{
private ObservableCollection<SiteViewModel> _AllSites;
public ObservableCollection<SiteViewModel> AllSites {
get { return _AllSites; }
set { SetProperty<ObservableCollection<SiteViewModel>>(ref _AllSites, value); }
}
public SitesViewModel()
{
AllSites = new ObservableCollection<SiteViewModel>();
for (int count = 1; count <= 3; count++)
{
AllSites.Add(new SiteViewModel { SiteID = count, Name = "Test" + count.ToString(), Description = "Site:" + count.ToString() } );
}
}
}
SiteManagerControl : UserControl with a SitesViewModel property _AllSites
public partial class SiteManagerControl : UserControl
{
private SitesViewModel _AllSites;
public SitesViewModel AllSites
{
get { return _AllSites; } //<-- Breakpoint not hit!
set {
if (_AllSites != value)
{ _AllSites = value;
OnPropertyChanged("AllSites");
}}
}
public SiteManagerControl(){
_AllSites = new SitesViewModel();}
(XAML can be seen in the first linked image above, Note the breakpoint not hit line in the above). The user control is hosted in a Tabcontrol that is part of an ObservableCollection. I don't think this is an issue in the databinding. Will post the code for the tabs if needed.
There are no errors in the Debug Output window to indicate why the databinding is failing.
Your listview DataContext Data binding is with object from class(SitesViewModel)
This class has property named (AllSites) that has oveservable collection property named (AllSites) as well.
so I think you have to fix the ItemSource binding in the list view like this:
ItemsSource="{Binding AllSites.AllSites}"
Will's comment (thanks!) above pointed me in the right direction: Changed MainWindow.xaml to contain:
<DataTemplate DataType="{x:Type vm:SitesViewModel}">
<uc:SitesView></uc:SitesView>
</DataTemplate>
Also followed this : http://codingtales.blogspot.co.uk/2010/02/creating-complete-tabbed-interface-in.html to rework my tab interface
something i do wrong? anyone gives some suggestions
according to msdn
Indexers of a property can be specified within square brackets following the property name where the indexer is applied. For instance, the clause Path=ShoppingCart[0] sets the binding to the index that corresponds to how your property's internal indexing handles the literal string "0". Multiple indexers are also supported.
i put Indexers of a property in my xaml
<Image Source="{Binding ImagePathList[0]}" Height="50" Width="50" Grid.Row="0" Grid.Column="0" VerticalAlignment="Top" Margin="0,7,7,0" Grid.RowSpan="2">
i do not give the viewmodel code because i am pretty sure ListImagePathList have data.
EDIT*
more detail: ImagePathList[0] is a web image url
EDIT FOR Brendan
model is Article
public class Article : INotifyPropertyChanged
{
private long _Id;
public long ID
{
get { return _Id; }
set
{
if (_Id != value)
{
_Id = value;
NotifyPropertyChanged();
}
}
}
private string _subject;
public string Subject
{
get
{
return _subject;
}
set
{
if (_subject != value)
{
_subject = value;
NotifyPropertyChanged();
}
}
}
private string _words;
public string Words
{
get
{
return _words;
}
set
{
if (_words != value)
{
_words = value;
NotifyPropertyChanged();
}
}
}
private DateTime _publishDate;
public DateTime PublishDate
{
get
{ return _publishDate; }
set
{
if (_publishDate != value)
{
_publishDate = value;
NotifyPropertyChanged();
}
}
}
public List<string> ImagePathList = new List<string>();
private string _firstImage;
public string FirstImage
{
get
{
return _firstImage;
}
set
{
if (_firstImage != value)
{
_firstImage = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
ArticleViewModel is in below; All data returned from network is correct!
public class ArticleListViewModel : INotifyPropertyChanged
{
public ArticleListViewModel()
{
this.ArticleCollection = new ObservableCollection<Article>();
}
public ObservableCollection<Article> ArticleCollection
{
get;
private set;
}
public void LoadPage(int pageNumber)
{
if (pageNumber == 1)
{
this.ArticleCollection.Clear();
}
IsLoading = true;
ReadArticleList(pageNumber);
}
private async void ReadArticleList(int pageNumber)
{
try
{
List<Article> articleList = new List<Article>();
articleList = await CollectionHttpClient.GetArticlesByPageAsync(pageNumber);
this.ArticleCollection.Add(item);
}
}
catch(Exception ex)
{
if (ex.HResult == -2146233088 && ex.Message.Equals("Response status code does not indicate success: 404 ()."))
{
MessageBox.Show("The network is not set right. Internet cannot be accessed.");
}
else
{
MessageBox.Show("sorry, no data.");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The XAML code you show is fine.
There may be a problem with the DataContext. Maybe the page DataContext has not been set? Or maybe the the DataContext has changed e.g. inside an ItemTemplate
Otherise the problem is probably to do with the bound property. Try the following
private ObservableCollection<string> _imagePathList = new ObservableCollection<string>();
public ObservableCollection<string> ImagePathList {
get { return this._imagePathList; }
set {
if (this._imagePathList != value)
{
this._imagePathList = value;
// I'm going to assume you have the NotifyPropertyChanged
// method defined on the view-model
this.NotifyPropertyChanged();
}
}
}
ObservableCollection is in System.Collections.ObjectModel and is like List but if elements are added/removed then the PropertyChanged event is fired. Also note that any bound property must have a get associated with it for it to work at all.
Another possibility is that ImagePathList was not assigned to or is empty - in which case, make sure you assign to it!
In case you have not yet implemented the NotifyPropertyChanged method, here it is ...
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
You will also need to add INotifyPropertyChanged interface to the containing class e.g.
public class MyViewModelClass : INoftifyPropertyChanged
{
...
}
I think the problem concerns the place where your images come from. As it is said at MSDN:
You can set the Source by specifying an absolute URL (e.g. http://contoso.com/myPicture.jpg) or specify a URL relative to the XAP file of your application.
You can set this property in XAML, but in this case you are setting the property as a URI. The XAML behavior relies on underlying type conversion that processes the string as a URI, and calls the BitmapImage(Uri) constructor. This in turn potentially requests a stream from that URI and returns the image source object.
Just for test - place some images in your project and then set ImagePathList to them. See if that works (it should as I think). Or see if you can get BitmapImage(ImagePathList);
It's hard for me to say now (as I don't know how your ImagePathList looks like) what could be a reason of the failure. But if I were you, I would test this.
I would advise to use a property (or you can use converter which will also do the job):
// a property in your class
public Uri FirstImage
{
get
{
return new Uri(ImagePathList.FirstOrDefault(), UriKind.Absolute);
}
}
On the other hand if you are downloading images to IsolatedStorage then you will have to use another property, that will load BitmapImage from IS:
public BitmapImage FirstImage // getter - BitmapImage
{
get
{
if (String.IsNullOrEmpty(ImagePath[0])) return null;
BitmapImage temp = new BitmapImage();
using (IsolatedStorageFile ISF = IsolatedStorageFile.GetUserStoreForApplication())
using (IsolatedStorageFileStream file = ISF.OpenFile(ImagePath[0], FileMode.Open, FileAccess.Read))
temp.SetSource(file);
return temp;
}
}
Hope this helps.
I want to pass a value from MainWindow into my UserControl! I passed a value to my UserControl and the UserControl showed me the value in a MessageBox, but it is not showing the value in a TextBox. Here is my code:
MainWindow(Passing Value To UserControl)
try
{
GroupsItems abc = null;
if (abc == null)
{
abc = new GroupsItems();
abc.MyParent = this;
abc.passedv(e.ToString(), this);
}
}
catch (Exception ee)
{
MessageBox.Show(ee.Message);
}
UserControl
public partial class GroupsItems : UserControl
{
public MainWindow MyParent { get; set; }
string idd = "";
public GroupsItems()
{
InitializeComponent();
data();
}
public void passedv(string id, MainWindow mp)
{
idd = id.ToString();
MessageBox.Show(idd);
data();
}
public void data()
{
if (idd!="")
{
MessageBox.Show(idd);
texbox.Text = idd;
}
}
}
EDIT(using BINDING and INotifyProperty )
.....
public GroupsItems()
{
InitializeComponent();
}
public void passedv()
{
textbox1.Text = Text;
}
}
public class Groupitm : INotifyPropertyChanged
{
private string _text = "";
public string Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Problem here is with reference.
When you create new object in code behind, new object will be created and this is not the same object which you have in xaml code. So you should use following code:
<local:GroupsItems x:Name="myGroupsItems"/>
and in code behind you don't have to create new object. You should use object that you added in XAML:
...
myGroupsItems.MyParent = this;
myGroupsItems.passedv(e.ToString(), this);
...
Here is example solution (sampleproject).
You are calling data in the constructor when idd is still "" which results in the text box still being empty. Changing the MyParent property does not change that. Only passedv does. But at that point you do not have the parent set. Just call data in passedv, too.
Try this:
public partial class GroupsItems : UserControl
{
//properties and methods
private string idd="";
public string IDD
{
get{return idd;}
set{
idd=value;
textBox1.Text=idd;
}
}
//other properties and methods
}
Usage:
In your Main form:
abc = new GroupsItems();
abc.IDD="sometext";
MainGrid1.Children.Add(abc); //Grid or any other container for your UserControl
In your Binding example, your GroupItem class looks ok, except that you need to pass in the name of the changed property:
public string Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
NotifyPropertyChanged("Text");
}
}
}
Now, in GroupsItems, you shouldn't be accessing the TextBox. In WPF, we manipulate the data, not the UI... but as we use Binding objects to data bind the data to the UI controls, they automatically update (if we correctly implement the INotifyPropertyChanged interface).
So first, let's add a data property into your code behind (which should also implement the INotifyPropertyChanged interface) like you did in your GroupItem class:
private GroupItem _item = new GroupItem();
public GroupItem Item
{
get { return _item; }
set
{
if (value != _item)
{
_item = value;
NotifyPropertyChanged("Item");
}
}
}
Now let's try using a Binding on a TextBox.Text property:
<TextBox Text="{Binding Item.Text}" />
See how we bind the Text property of your GroupItem class to the TextBox.Text property... now all we need to do is to change the value of the Item.Text property and watch it update in the UI:
<Button Content="Click me" Click="Button_Click" />
...
private void Button_Click(object sender, RoutedEventArgs e)
{
Item.Text = "Can you see me now?";
}
Alternatively, you could put this code into your passedv method if you are calling that elsewhere in your project. Let me know how you get on.
UPDATE >>>
In your GroupItem class, try changing the initialization to this:
private string _text = "Any text value";
Can you now see that text in the UI when you run the application? If not, then try adding/copying the whole Text property into your code behind and changing the TextBox declaration to this:
<TextBox Text="{Binding Text}" />
If you can't see the text value now, you've really got problems... you have implemented the INotifyPropertyChanged interface in your code behind haven't you?
I am having an issue getting my model changes updated back into my viewmodel so i can display. In this example i have a label and a button, when i press the button it will execute some business logic, and should update the label on screen. However, when my model changes the view will not. Any Idea on what i am doing wrong here?
View-
<Window.DataContext>
<vm:ViewModel>
</Window.DataContext>
<Grid>
<Label Content="{Binding Path=Name}"/>
<Button Command={Binding UpdateBtnPressed}/>
</Grid>
ViewModel
public ViewModel()
{
_Model = new Model();
}
public string Name
{
get{return _Model.Name;}
set
{
_Model.Name = value;
OnPropertyChanged("Name");
}
}
public ICommand UpdateBtnPressed
{
get{
_UpdateBtn = new RelayCommand(param => UpdateLabelValue());
return _UpdateBtn;
}
private void UpdateLabelValue()
{
_Model.Name = "Value Updated";
}
Model
private string name = "unmodified string";
public string Name
{
get{return name;}
set{name = value;}
}
Try this:
private void UpdateLabelValue()
{
Name = "Value Updated";
}
It seems you've missed to implement the INotifyPropertyChanged interface.
Your model must implement INotifyPropertyChanged such as;
public class Personel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _name;
public string Name
{
get { return _name; }
set { _name = value; OnChanged("Name");}
}
void OnChanged(string pn)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(pn));
}
}
}