Update a listview from modify on selected items - c#

Excuse me if I didn't formulate good the question, but don't know how to name it better...
I have a project with some ListView, binded to ObservableCollection.
When I make a right click on my ListView, to change name, or other parameter, the ListView doesn't automatically refresh until I go out, then open again.
Here is my behind code for context :
public class Contexte : INotifyPropertyChanged
{
private Affaire affaireSelectionnee;
public Affaire AffaireSelectionnee
{
get { return affaireSelectionnee; }
set
{
if (value == affaireSelectionnee) return;
affaireSelectionnee = value;
NotifyPropertyChanged("AffaireSelectionnee");
}
}
private ObservableCollection<Affaire> listeDesAffairesSelectionnees;
public ObservableCollection<Affaire> ListeDesAffairesSelectionnees
{
get { return listeDesAffairesSelectionnees; }
set { NotifyPropertyChanged(ref listeDesAffairesSelectionnees, value); }
}
private ObservableCollection<Phase> listeDesPhasesSelectionnees;
public ObservableCollection<Phase> ListeDesPhasesSelectionnees
{
get { return listeDesPhasesSelectionnees; }
set { NotifyPropertyChanged(ref listeDesPhasesSelectionnees, value); }
}
private ObservableCollection<Assemblage> listeDesAssemblagesSelectionnees;
public ObservableCollection<Assemblage> ListeDesAssemblagesSelectionnees
{
get { return listeDesAssemblagesSelectionnees; }
set { NotifyPropertyChanged(ref listeDesAssemblagesSelectionnees, value); }
}
private ObservableCollection<Repere> listeDesReperesSelectionnees;
public ObservableCollection<Repere> ListeDesReperesSelectionnees
{
get { return listeDesReperesSelectionnees; }
set { NotifyPropertyChanged(ref listeDesReperesSelectionnees, value); }
}
private ObservableCollection<Affaire> listeDesAffaires;
public ObservableCollection<Affaire> ListeDesAffaires
{
get { return listeDesAffaires; }
set { NotifyPropertyChanged(ref listeDesAffaires, value); }
}
private ObservableCollection<Phase> listeDesPhases;
public ObservableCollection<Phase> ListeDesPhases
{
get { return listeDesPhases; }
set { NotifyPropertyChanged(ref listeDesPhases, value); }
}
private ObservableCollection<Assemblage> listeDesAssemblages;
public ObservableCollection<Assemblage> ListeDesAssemblages
{
get { return listeDesAssemblages; }
set { NotifyPropertyChanged(ref listeDesAssemblages, value); }
}
private ObservableCollection<Repere> listeDesReperes;
public ObservableCollection<Repere> ListeDesReperes
{
get { return listeDesReperes; }
set { NotifyPropertyChanged(ref listeDesReperes, value); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string nomPropriete)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(nomPropriete));
}
private bool NotifyPropertyChanged<T>(ref T variable, T valeur, [CallerMemberName] string nomPropriete = null)
{
if (object.Equals(variable, valeur)) return false;
variable = valeur;
NotifyPropertyChanged(nomPropriete);
return true;
}
}
I load the context once when loading the programm :
DBConnect DataBase = new DBConnect();
string requete = "SELECT * FROM affaire ORDER BY ID";
List<Affaire> liste = DataBase.Select_affaire(requete, true);
contexte = new Contexte { ListeDesAffaires = new ObservableCollection<Affaire>(liste), ListeDesPhases = new ObservableCollection<Phase>(), ListeDesAssemblages = new ObservableCollection<Assemblage>(), ListeDesReperes = new ObservableCollection<Repere>(), AffaireSelectionnee = new Affaire(), ListeDesAffairesSelectionnees = new ObservableCollection<Affaire>(liste), ListeDesPhasesSelectionnees = new ObservableCollection<Phase>(), ListeDesAssemblagesSelectionnees = new ObservableCollection<Assemblage>(), ListeDesReperesSelectionnees = new ObservableCollection<Repere>() };
DataContext = contexte;
Then my function that may update property :
foreach (Phase ph in contexte.ListeDesPhasesSelectionnees)
{
Phase ph_find = contexte.ListeDesPhases.First(s=>s==ph);
ph_find.Priorite = new_priorite;
}
ph_find.Priorite is well updated, as is my Observable Collection "contexte.ListeDesPhases", but no refresh is made on the ListView.
Edit : Well I could solve the problem adding a ListView1.Items.Refresh()...
I am not sure this is the most correct way(is not bidding supposed to refresh the listview automaticaly?), but for now it works
Edit2 :
My XAML code (ListView of the phase) :
<ListView x:Name="ListView2" ItemsSource="{Binding ListeDesPhases}" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="4" MouseDoubleClick="ListView_MouseDoubleClick" GridViewColumnHeader.Click="GridViewColumnHeaderClickedHandler" SelectionChanged="ListView_SelectionChanged" >
<ListView.View>
<GridView AllowsColumnReorder="true" x:Name="GridView2">
<GridViewColumn DisplayMemberBinding="{Binding ID}" Header="ID" Width="50"/>
<GridViewColumn DisplayMemberBinding= "{Binding NomPhase}" Header="{x:Static p:Resources.Nom}" Width="200"/>
<GridViewColumn DisplayMemberBinding="{Binding IdAffaire}" Header="{x:Static p:Resources.IdAffaire}" Width="100"/>
<GridViewColumn DisplayMemberBinding="{Binding CommPhase}" Header="{x:Static p:Resources.Commentaire}" Width="100"/>
<GridViewColumn DisplayMemberBinding="{Binding Priorite}" Header="{x:Static p:Resources.Priorite}" Width="100"/>
</GridView>
</ListView.View>
</ListView>
My Phase class :
public class Phase
{
public string NomPhase { get; set; }
public long IdAffaire { get; set; }
public string CommPhase { get; set; }
public int Priorite { get; set; }
public long ID { get; set; }
public List<Assemblage> ListAssemblages { get; set; }
public Phase()
{
this.NomPhase = "";
this.IdAffaire = 0;
this.CommPhase = "";
this.Priorite = 0;
this.ID = 0;
this.ListAssemblages = new List<Assemblage>();
}
...
}
Edit3 :
Tried to modify as indicated by Netstep, but still the same :
public ObservableCollection<Phase> ListeDesPhases
{
get { return listeDesPhases; }
set { NotifyPropertyChanged(ref listeDesPhases, value);
NotifyPropertyChanged("Priorite");
}
}
Edit 4 :
Well, I now understand that nothing was happening, I read that course http://www.wpf-tutorial.com/data-binding/responding-to-changes/ to understand it...
So example given by NetStep was the good one (just didn't understand what is the RaisePropertyChanged(() => Priorite); part? Is this due to the use of mvvmlight.net library?
public class Phase : INotifyPropertyChanged
{
private string nomPhase;
public string NomPhase
{
get { return this.nomPhase; }
set
{
if (this.nomPhase != value)
{
this.nomPhase = value;
this.NotifyPropertyChanged("NomPhase");
}
}
}
private int priorite;
public int Priorite
{
get { return this.priorite; }
set
{
if (this.priorite != value)
{
this.priorite = value;
this.NotifyPropertyChanged("Priorite");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
...//all of my other functions
}
So this is what I did and it works good now.
Then I have the following question : I have 4 different objects displayed in 4 ListView(Contract, Subcontract, Phase and Detail).
Contract is the "Mother Class", it countains parameters, but also contains a list of Subcontracts. Subcontracts contains several parameters, and a list of Phases, and each Phase contains some parameters, with a list of Details.
Each of them is displayed in a different ListView(4 ListView).
May I define 4 different ObservableCollection, or is there a way to define only one ObservableCollection for all the "tree", then bind on parameters of my ObservableCollection>?

To have the field properly updated in UI, please ensure that Phase class also implement INotifyPropertyChanged someway and call
NotifyPropertyChanged("Priorite")
in set accessor. Just using the ObservableCollection is not enough, it handles notifying only Add/Remove operations. And you right - ListView1.Items.Refresh() is a workaround in this case.
Hope this will help, otherwise please share Phase class code and your xaml code to get more clear comment/answer.
Here is the example, based on MVVM light library:
using GalaSoft.MvvmLight;
namespace WpfApp1
{
public class Phase : ViewModelBase
{
private int _priorite;
public int Priorite
{
get { return _priorite; }
set
{
_priorite = value;
RaisePropertyChanged(() => Priorite);
}
}
}
}
All the rest of code can remain unchanged. You also can inherit you Context class from ViewModelBase

Related

Trying to update an image source in an imagebutton with data binding using a command in the viewmodel - not working

Please forgive the newbie question but I am struggling to understand where I have gone wrong...
I am trying to change an imagebutton (in a grid) by binding the image source in xaml:
<ImageButton x:Name="playButton"
Source="{Binding PlayImage}"
Command="{Binding PlayCommand}"
Grid.Row="1"
Grid.Column="0"
BorderColor="#fafafa"
BackgroundColor="#fafafa"
/>
The ImageButton loads up with the correct 'play.png' initially.
The Command 'PlayCommand' is working with the binding. This should change the value of the PlayImage to show the 'pause.png' image when the user clicks the imagebutton. Although the value of the PlayImage variable is changed, the image will not update. Please can someone tell me what I am missing? Here is my ViewModel:
public class SermonDetailViewModel : BaseViewModel
{
public Sermon Sermon { get; set; }
public ICommand PlayCommand { private set; get; }
private ImageSource _playImage;
public ImageSource PlayImage
{
get { return _playImage; }
set
{
_playImage = value;
SetProperty(ref _playImage, value);
}
}
public SermonDetailViewModel(Sermon sermon = null)
{
if (sermon != null)
{
Title = sermon.STitle;
MP3Filepath = sermon.SLink;
PlayCommand = new Command(async () => await StartPlayer());
_playImage = "play.png";
}
Sermon = sermon;
}
async Task StartPlayer()
{
await CrossMediaManager.Current.Play(MP3Filepath);
_playImage = "pause.png";
Console.WriteLine(_playImage);
Console.WriteLine(PlayImage);
}
and this is my baseViewModel code which uses the class INotifyPropertyChanged and sets up the setProperty method:
public class BaseViewModel : INotifyPropertyChanged
{
public IDataStore<Item> DataStore => DependencyService.Get<IDataStore<Item>>();
bool isBusy = false;
public bool IsBusy
{
get { return isBusy; }
set { SetProperty(ref isBusy, value); }
}
string title = string.Empty;
public string Title
{
get { return title; }
set { SetProperty(ref title, value); }
}
string mp3filepath = string.Empty;
public string MP3Filepath
{
get { return mp3filepath; }
set { SetProperty(ref mp3filepath, value); }
}
protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName]string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
I would really appreciate some help here.....thanks!
I try your code and do one sample to test, and I agree with Jason's opinion, please try to set PlayImage value, don't set _playImage, I find that although the value of PlayImage has been updated, it is not bound to ImageButton.
Please change the code like following code:
public SermonDetailViewModel(Sermon sermon = null)
{
if (sermon != null)
{
Title = sermon.STitle;
MP3Filepath = sermon.SLink;
PlayCommand = new Command(async () => await StartPlayer());
PlayImage= "play.png";
}
Sermon = sermon;
}
async Task StartPlayer()
{
await CrossMediaManager.Current.Play(MP3Filepath);
PlayImage= "pause.png";
Console.WriteLine(_playImage);
Console.WriteLine(PlayImage);
}
I also do one sample that you can take a look:
<ImageButton
BackgroundColor="#fafafa"
BorderColor="#fafafa"
Command="{Binding PlayCommand}"
HeightRequest="50"
Source="{Binding PlayImage}"
WidthRequest="50" />
public partial class Page4 : ContentPage
{
public Page4()
{
InitializeComponent();
this.BindingContext = new SermonDetailViewModel();
}
}
public class SermonDetailViewModel:ViewModelBase
{
public ICommand PlayCommand { private set; get; }
private ImageSource _playImage;
public ImageSource PlayImage
{
get { return _playImage; }
set
{
_playImage = value;
RaisePropertyChanged("PlayImage");
}
}
public SermonDetailViewModel()
{
PlayCommand = new Command(method1);
_playImage = "check.png";
}
private void method1()
{
PlayImage = "plu3.png";
}
}
Try
Source="{Binding PlayImage, Mode=TwoWay }"
This can also help you with binding
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/data-binding/binding-mode
you are setting the private backing field, not the public property. This means that the PropertyChanged event will not be raised
async Task StartPlayer()
{
await CrossMediaManager.Current.Play(MP3Filepath);
// should be PlayImage = "pause.png";
_playImage = "pause.png";
Console.WriteLine(_playImage);
Console.WriteLine(PlayImage);
}
It works with the code:
private ImageSource _playImage;
public ImageSource PlayImage
{
get { return _playImage; }
set
{
_playImage = value;
Notify("PlayImage");
}
}
protected void Notify(string propertyName)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

.net c# Limit number of entries in observablecollection

I having a WPF application in which the UI has a list box. The list box has binding of ObservableCollection. Log class implements INotifyPropertyChanged.
The list will show the continuous logging of the application. As long as the application is running. The ObservableCollection size keeps on growing. After some time I get the Out of Memory exception. I want to show the latest 1000 entries in the list control. Any suggestions on this will be of great help!!
XAML:
<DataGrid AutoGenerateColumns="False" SelectedValue="{Binding SelectedLog}" SelectionUnit="FullRow" SelectionMode="Single" Name="dataGridLogs"
ItemsSource="{Binding Path=LogList}" CanUserReorderColumns="True" CanUserResizeRows="True" CanUserDeleteRows="False" IsReadOnly="True"
CanUserAddRows="False" EnableColumnVirtualization="True" EnableRowVirtualization="True" SelectionChanged="grid_SelectionChanged">
<DataGrid.Columns>
<DataGridTextColumn Header="Time Stamp" Binding="{Binding StrTimeStamp, Mode=OneWay}" Width="Auto"/>
<DataGridTextColumn Header="Action" Binding="{Binding Action, Mode=OneWay}" Width="Auto"/>
</DataGrid>
ViewModel:
public ObservableCollection<LogData> LogList
{
get
{
if (logList == null)
{
logList = new ObservableCollection<LogData>();
}
return logList;
}
set
{
logList = value;
OnPropertyChanged("LogList");
}
}
model:
public class LogData : INotifyPropertyChanged
{
public LogData()
{
}
private String timestamp = string.Empty;
public String StrTimestamp
{
get
{
if (timestamp == null)
return string.Empty;
return timestamp ;
}
set
{
timestamp = value;
}
}
public string Action
{
get;set;
}
}
You could create your own size limited observable collection class. Something like this should get you started:
public class LimitedSizeObservableCollection<T> : INotifyCollectionChanged
{
private ObservableCollection<T> _collection;
private bool _ignoreChange;
public LimitedSizeObservableCollection(int capacity)
{
Capacity = capacity;
_ignoreChange = false;
_collection = new ObservableCollection<T>();
_collection.CollectionChanged += _collection_CollectionChanged;
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
public int Capacity {get;}
public void Add(T item)
{
if(_collection.Count = Capacity)
{
_ignoreChange = true;
_collection.RemoveAt(0);
_ignoreChange = false;
}
_collection.Add(item);
}
private void _collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if(!_ignoreChange)
{
CollectionChanged?.Invoke(this, e);
}
}
}
Of course, you will probably have to expose some more methods, but I hope that's enough for you to get the idea.
it can be easily done by this class:
public class LimitedSizeObservableCollection<T> : ObservableCollection<T>
{
public int Capacity { get; }
public LimitedSizeObservableCollection(int capacity)
{
Capacity = capacity;
}
public new void Add(T item)
{
if (Count >= Capacity)
{
this.RemoveAt(0);
}
base.Add(item);
}
}
I found another way to limit the number of elements in the collection, without adding a "new" method that breaks compatibility with parent classes:
public class LimitedSizeObservableCollection<T> : ObservableCollection<T>
{
public int Capacity { get; set; } = 0;
protected override void InsertItem(int index, T item)
{
if (this.Capacity > 0 && this.Count >= this.Capacity)
{
throw new Exception(string.Format("The maximum number of items in the list ({0}) has been reached, unable to add further items", this.Capacity));
}
else
{
base.InsertItem(index, item);
}
}
}
If you want it should not add to the collection more than 1000 you can do this.
public ObservableCollection<LogData> LogList
{
get
{
if (logList == null)
{
logList = new ObservableCollection<LogData>();
}
return logList;
}
set
{
if(LogList.Count < 1001)
{
logList = value;
OnPropertyChanged("LogList");
}
}
}
or you can remove the old entries when adding new more than 1000
public ObservableCollection<LogData> LogList
{
get
{
if (logList == null)
{
logList = new ObservableCollection<LogData>();
}
return logList;
}
set
{
if(LogList.Count < 1001)
{
logList = value;
OnPropertyChanged("LogList");
}
else
{
LogList.RemoveAt(0);
logList = value;
OnPropertyChanged("LogList");
}
}
}

how to use relational tables in a WPF viewmodel

I'm building a N tier WPF app. I want zero codebehind.
Let's say I have 3 normalized related tables to record sales transactions.
TRANSACTIONS:
TransactionId,
ItemId,
SupplierId,
Price
SUPPLIERS:
SupplierId,
SupplierName
ITEMS:
ItemId,
ItemName.
For each table I have a Base class that reflects the fields. Then a data layer that populates a collection of base objects as required.
I want to have a Listbox on the page showing a list of all of the transactions, 1 transaction per row, the rows should look something like this...
"Trainers FootLocker €99"
"Trousers TopShop €45"
"Coat TopShop €49"
If I use the
<ListBox
ItemsSource="{Binding Path=Transactions}"
SelectedItem="{Binding CurrentTransaction}"
then I end up with rows of IDs from the Transactions table and not the Name values from the Items and Suppliers tables.
Given that I have collection of transactions filled with only IDs to the other tables, what is the best approach to populating the listbox?
One thing I'm wondering is, should my Transactions Base object contain Item item populated there instead of int ItemId?
Transaction Base Model:
using System;
using System.ComponentModel;
using PFT;
using PFT.Data;
namespace PFT.Base
{
public class Transaction : INotifyPropertyChanged
{
public int Id { get; set; }
private int _itemId;
public int ItemId
{
get { return _itemId; }
set {
_itemId = value;
ItemData id = new ItemData();
this.Item = id.Select(value);
NotifyPropertyChanged("ItemId");
}
}
private Item _item;
public Item Item
{
get { return _item; }
set { _item = value; }
}
private float _price;
public float Price
{
get { return _price; }
set {
_price = value;
NotifyPropertyChanged("Price");
}
}
private DateTime _date;
public DateTime Date
{
get { return _date; }
set {
_date = value;
NotifyPropertyChanged("Date");
}
}
private string _comment;
public string Comment
{
get { return _comment; }
set
{
_comment = value;
NotifyPropertyChanged("Comment");
}
}
private int _traderId;
public int TraderId
{
get { return _traderId; }
set
{
_traderId = value;
NotifyPropertyChanged("TraderId");
}
}
private Trader _trader;
public Trader Trader
{
get { return _trader; }
set { _trader = value;
TraderData t = new TraderData();
this.Trader = t.Select(value);
}
}
private string _insertType;
/// <summary>
/// A - Auto, M - Manual, V - Verified
/// </summary>
public string InsertType
{
get { return _insertType; }
set { _insertType = value;
NotifyPropertyChanged("InsertType");
}
}
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
//private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
ITEMS BASE CLASS
using System.ComponentModel;
namespace PFT.Base
{
public class Item : INotifyPropertyChanged
{
private int _id;
public int Id
{
get { return _id; }
set { _id = value;
NotifyPropertyChanged("Id");
}
}
private string _name;
public string Name
{
get { return _name; }
set { _name = value;
NotifyPropertyChanged("Name");
}
}
private string _description;
public string Description
{
get { return _description; }
set { _description = value;
NotifyPropertyChanged("Description");
}
}
private float _defaultPrice;
public float DefaultPrice
{
get { return _defaultPrice; }
set { _defaultPrice = value;
NotifyPropertyChanged("DefaultPrice");
}
}
private bool _isIncome;
public bool IsIncome
{
get { return _isIncome; }
set { _isIncome = value;
NotifyPropertyChanged("IsIncome");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
The way you'd do this with viewmodels would be to give Transaction a Supplier property and an Item property. Those properties would be references to the actual Item and Supplier objects in their own collections. If the relationship is one ItemID and one SupplierID per transaction, that's the object equivalent. If a transaction can be multiple records with the same transaction ID and different supplier or item IDs, then Transaction needs collections of Item and Supplier. We can do that in WPF too, but it'll take a lot more angle brackets than the trivial example below.
You would set that up when you get your items from the database (however you're doing that), or maybe Entity Framework can do that for you.
Real simple listbox displaying item names: Add DisplayMemberPath.
<ListBox
ItemsSource="{Binding Transactions}"
SelectedItem="{Binding CurrentTransaction}"
DisplayMemberPath="Item.Name"
/>
More complicated:
<ListBox
ItemsSource="{Binding Transactions}"
SelectedItem="{Binding CurrentTransaction}"
>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding Item.Name, Mode=OneWay}" />
<Run Text=" - " />
<Run Text="{Binding Supplier.Name, Mode=OneWay}" />
<Run Text=" " />
<Run Text="{Binding Price, Mode=OneWay, StringFormat=c}" />
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You could also look into a columned control like a ListView or DataGrid.
Slightly off topic, zero code-behind is a bit extreme. It's a last resort, not a third rail. Minimal code-behind is a sound general principle. Don't go crazy trying to avoid it; it's there for a reason.
UPDATE
public class Transaction : INotifyPropertyChanged
{
// ... stuff ...
public Item Item
{
get { return _item; }
set {
_item = value;
// If this property is ever set outside the Transaction
// constructor, you ABSOLUTELY MUST raise PropertyChanged here.
// Otherwise, make the setter private. But just raise the event.
// This has nothing whatsoever to do with when or whether the Item
// class raises PropertyChanged, because this is not a property of the
// Item class. This is a property of Transaction.
NotifyPropertyChanged("Item");
}
}
// ... more stuff ...

WPF- How to update the changes in list item of a list

i have updated a list item of a list.The item is successfully updated at source i.e database ,but the list is not getting updated with updated item.I have used INotifyPropertyChanged interface for the list items and the list is binded to an observable collection.
private tbl_Model _modelItem;
public tbl_Model ModelItem
{
get { return _modelItem; }
private set
{
_modelItem = value;
NotifyPropertyChanged("ModelItem");
}
}
private ObservableCollection<tbl_Model> _modelCollection;
public ObservableCollection<tbl_Model> ModelCollection
{
get { return _modelCollection; }
private set
{
_modelCollection = value;
NotifyPropertyChanged("ModelCollection");
}
}
public void btn_update()
{
//Code to update at database
//what should i write here to update the list ?
}
As you can see in image,the list shows model no. as 101 even after i updated it to 102
Thanks in advance
The auto generated model via Linq to Sql
public partial class tbl_Model : INotifyPropertyChanging, INotifyPropertyChanged
{
private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);
private int _ID;
private string _Model_No;
private string _Name;
private string _Manufacturer;
private int _IsDelete;
#region Extensibility Method Definitions
partial void OnLoaded();
partial void OnValidate(System.Data.Linq.ChangeAction action);
partial void OnCreated();
partial void OnIDChanging(int value);
partial void OnIDChanged();
partial void OnModel_NoChanging(string value);
partial void OnModel_NoChanged();
partial void OnNameChanging(string value);
partial void OnNameChanged();
partial void OnManufacturerChanging(string value);
partial void OnManufacturerChanged();
partial void OnIsDeleteChanging(int value);
partial void OnIsDeleteChanged();
#endregion
public tbl_Model()
{
OnCreated();
}
[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_ID", AutoSync=AutoSync.OnInsert, DbType="Int NOT NULL IDENTITY", IsPrimaryKey=true, IsDbGenerated=true)]
public int ID
{
get
{
return this._ID;
}
set
{
if ((this._ID != value))
{
this.OnIDChanging(value);
this.SendPropertyChanging();
this._ID = value;
this.SendPropertyChanged("ID");
this.OnIDChanged();
}
}
}
[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Model_No", DbType="VarChar(50) NOT NULL", CanBeNull=false)]
public string Model_No
{
get
{
return this._Model_No;
}
set
{
if ((this._Model_No != value))
{
this.OnModel_NoChanging(value);
this.SendPropertyChanging();
this._Model_No = value;
this.SendPropertyChanged("Model_No");
this.OnModel_NoChanged();
}
}
}
[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Name", DbType="VarChar(50) NOT NULL", CanBeNull=false)]
public string Name
{
get
{
return this._Name;
}
set
{
if ((this._Name != value))
{
this.OnNameChanging(value);
this.SendPropertyChanging();
this._Name = value;
this.SendPropertyChanged("Name");
this.OnNameChanged();
}
}
}
[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Manufacturer", DbType="VarChar(50) NOT NULL", CanBeNull=false)]
public string Manufacturer
{
get
{
return this._Manufacturer;
}
set
{
if ((this._Manufacturer != value))
{
this.OnManufacturerChanging(value);
this.SendPropertyChanging();
this._Manufacturer = value;
this.SendPropertyChanged("Manufacturer");
this.OnManufacturerChanged();
}
}
}
[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_IsDelete", DbType="Int NOT NULL")]
public int IsDelete
{
get
{
return this._IsDelete;
}
set
{
if ((this._IsDelete != value))
{
this.OnIsDeleteChanging(value);
this.SendPropertyChanging();
this._IsDelete = value;
this.SendPropertyChanged("IsDelete");
this.OnIsDeleteChanged();
}
}
}
public event PropertyChangingEventHandler PropertyChanging;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void SendPropertyChanging()
{
if ((this.PropertyChanging != null))
{
this.PropertyChanging(this, emptyChangingEventArgs);
}
}
protected virtual void SendPropertyChanged(String propertyName)
{
if ((this.PropertyChanged != null))
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The ListView XAML Code
<ListView ItemsSource="{Binding ModelCollection,Mode=TwoWay}" SelectedItem="{Binding SelectedModelItem}" Style="{StaticResource viewinglist}">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Model_No, Mode=TwoWay}" Header="Model No." Width="100"/>
<GridViewColumn DisplayMemberBinding="{Binding Name, Mode=TwoWay}" Header="Model Name" Width="200"/>
<GridViewColumn DisplayMemberBinding="{Binding Manufacturer, Mode=TwoWay}" Header="Manufacturer" Width="200"/>
</GridView>
</ListView.View>
</ListView>
Solution for anyone who sees the post:
This is where i was doing wrong-:
public tbl_Model SelectedModelItem {get; set;}
//on clicking edit this is what i used to do
ModelItem.ID = SelectedModelItem.ID;
ModelItem.Model_No = SelectedModelItem.Model_No;
ModelItem.Name = SelectedModelItem.Name;
ModelItem.Manufacturer = SelectedModelItem.Manufacturer;
The right way :
private tbl_Model _selectedModelItem;
public tbl_Model SelectedModelItem
{
get { return _selectedModelItem; }
set
{
_selectedModelItem = value;
NotifyPropertyChanged("SelectedModelItem");
}
}
on clicking edit
ModelItem = SelectedModelItem;
are you really sure that you update a ModelItem from WITHIN your Collection? you did not post the code.
The bindings and INotifyPropertyChanged implementation looks well.
<ListView ItemsSource="{Binding ModelCollection,Mode=TwoWay}"
SelectedItem="{Binding SelectedModelItem}" />
private tbl_Model _modelItem;
public tbl_Model SelectedModelItem
{
get { return _modelItem; }
private set
{
_modelItem = value;
NotifyPropertyChanged("SelectedModelItem");
}
}
public void Update()
{
SelectedModelItem.Model_No = "102";//Ui get notified, cause its a ModelItem from your Collection
}
ps: and pls remove the TwoWay Binding from
<ListView ItemsSource="{Binding ModelCollection,Mode=TwoWay}"
your ListeView will never set a ModelCollection back to your Viewmodel.
The properties of ModelItem also needs to implement INotifyPropertyChanged.
You have to implement INotifyPropertyChanged in your model.
In case your model is auto generated, then you can create a ViewModel for your model which implements INotifyPropertyChanged.
Each property of your Model or ViewModel needs to yield property changed.
The ObservableCollection raises events automatically but for ModelItem's properties you have to raise the events yourself.
public class ModelItem : INotifyPropertyChanged
{
private int modelNumber;
public int ModelNumber
{
get { return modelNumber; }
set
{
modelNumber = value;
NotifyPropertyChanged("ModelNumber"); }
}
//Similar implementation for other Properties Model Name, Manufacturer
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}

WPF returning a List to the class

New to WPF and C# from VB web forms, so sorry for this poorly structured question I will add to as needed to improve. I am trying to implement an example by adding database calls to MySQL to populate an On-Demand Tree View control. Here is the link to the sample code...
sample code
Got my db connection working and data is populating my dataset. I iterate to place in a List. But can not seem to figure out the issue with passing the List to the Class to populate the control...
public class Level1
{
public Level1(string level1Name)
{
this.Level1Name = level1Name;
}
public string Level1Name { get; private set; }
readonly List<Level2> _level2s = new List<Level2>();
public List<Level2> Level2s
{
get { return _level2s; }
}
}
I have a database class that queries the db and parses the data....
List<string> level1s = new List<string>();
DataSet ds = new DataSet();
foreach (DataTable table in ds.Tables)
{
foreach (DataRow row in table.Rows)
{
level1s.Add((string)row["name"]);
}
}
**UPDATE**: Trying to return the list...
return new Level1[]
{
foreach(DataRow row in level1s)
{
// iterate here
}
};
My level1s List is properly populated, I am just drawing a blank on returning the values.
thanks,
UPDATE - I am including the ViewModel code here as well....
using BusinessLib;
namespace TreeViewWithViewModelTOC.LoadOnDemand
{
public class Level1ViewModel : TreeViewItemViewModel
{
readonly Level1 _level1;
public Level1ViewModel(Level1 level1)
: base(null, true)
{
_level1 = level1;
}
public string Level1Name
{
get { return _level1.Level1Name; }
}
protected override void LoadChildren()
{
foreach (Level2 level2 in Database.GetLevel2s(_level1))
base.Children.Add(new Level2ViewModel(level2, this));
}
}
}
Try like this below,
List<Level1> L1=new List<Level1>();
foreach(var row in level1s)
{
Level1 L=new Level1();
// L.Level1Name = row.ToString(); here add items as you need
L1.Add(L);
}
return L1.ToArray();
You should be using MVVM design pattern to solve this. There aren't many requirements listed in your questions so I will assume my own, which should lead you along the right path.
First thing is determining whether or not you're records are going to be ready/pulled at run-time--before the TreeView is rendered and if they will be changed/updated/added/removed from the structure during the lifecycle of the application. If the structure isn't going to be changed, you can continue to use List as your collection. If you're (or a user is) going to be adding/removing from the collection, ultimately changing the structure, then you need to notify the UI that a change occurred on the collection; so you would use the built in ObservableCollection for that. Here is a MVVM-purist solution, with the assumption that your data will be pulled at application startup and you will be modifying the collection:
Note: RelayCommand implementation was taken from here
Models
public class First
{
public string Name
{
get;
set;
}
public readonly List<Second> Children;
public First(string name)
{
Name = name;
Children = new List<Second>
{
new Second(1),
new Second(2),
new Second(3),
};
}
public void AddChild(Second child)
{
Children.Add(child);
ChildAdded(this, new ChildAddedEventArgs(child));
}
public EventHandler<ChildAddedEventArgs> ChildAdded;
}
public class ChildAddedEventArgs //technically, not considered a model
{
public readonly Second ChildAdded;
public ChildAddedEventArgs(Second childAdded)
{
ChildAdded = childAdded;
}
}
public class Second
{
public int Number
{
get;
set;
}
public Second(int number)
{
Number = number;
}
}
ViewModels
public class MainViewModel : INotifyPropertyChanged
{
private readonly ObservableCollection<FirstViewModel> _items;
private readonly ICommand _addFirstFirstChildCommand;
private readonly ICommand _addSecondFirstChildCommand;
private readonly ICommand _toggleExpandCollapseCommand;
private bool _firstAddedFlag;
public MainViewModel(IEnumerable<First> records)
{
_items = new ObservableCollection<FirstViewModel>();
foreach(var r in records)
{
_items.Add(new FirstViewModel(r));
}
_addFirstFirstChildCommand = new RelayCommand(param => AddFirst(), param => CanAddFirst);
_addSecondFirstChildCommand = new RelayCommand(param => AddSecond(), param => CanAddSecond);
_toggleExpandCollapseCommand = new RelayCommand(param => ExpandCollapseAll(), param =>
{
return true;
});
}
public ObservableCollection<FirstViewModel> Items
{
get
{
return _items;
}
}
public ICommand AddFirstFirstChildCommand
{
get
{
return _addFirstFirstChildCommand;
}
}
public ICommand AddSecondFirstChildCommand
{
get
{
return _addSecondFirstChildCommand;
}
}
public ICommand ToggleExpandCollapseCommand
{
get
{
return _toggleExpandCollapseCommand;
}
}
public bool CanAddFirst
{
get
{
return true;
}
}
public bool CanAddSecond
{
get
{
//Only allow second to be added if we added to first, first
return _firstAddedFlag;
}
}
public void AddFirstChild(FirstViewModel item)
{
Items.Add(item);
}
private void AddFirst()
{
_items[0].AddChild(new Second(10));
_firstAddedFlag = true;
}
private void AddSecond()
{
_items[1].AddChild(new Second(20));
}
private void ExpandCollapseAll()
{
foreach(var i in Items)
{
i.IsExpanded = !i.IsExpanded;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class FirstViewModel : INotifyPropertyChanged
{
private readonly First model;
private readonly ObservableCollection<SecondViewModel> _children;
private bool _isExpanded;
public FirstViewModel(First first)
{
_children = new ObservableCollection<SecondViewModel>();
model = first;
foreach(var s in first.Children)
{
Children.Add(new SecondViewModel(s));
}
model.ChildAdded += OnChildAdded;
}
public string FirstName
{
get
{
return model.Name;
}
set
{
model.Name = value;
NotifyPropertyChanged();
}
}
public ObservableCollection<SecondViewModel> Children
{
get
{
return _children;
}
}
public bool IsExpanded
{
get
{
return _isExpanded;
}
set
{
_isExpanded = value;
NotifyPropertyChanged();
}
}
internal void AddChild(Second second)
{
model.AddChild(second);
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void OnChildAdded(object sender, ChildAddedEventArgs args)
{
if(Children != null)
{
Children.Add(new SecondViewModel(args.ChildAdded));
}
}
}
public class SecondViewModel : INotifyPropertyChanged
{
private readonly Second model;
private bool _isExpanded;
public SecondViewModel(Second second)
{
model = second;
}
public int SecondNumber
{
get
{
return model.Number;
}
set
{
model.Number = value;
NotifyPropertyChanged();
}
}
//Added property to avoid warnings in output window
public bool IsExpanded
{
get
{
return _isExpanded;
}
set
{
_isExpanded = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Model Provider
public class Database
{
public static IEnumerable<First> GetChildren()
{
List<First> firsts = new List<First>();
firsts.Add(new First("John"));
firsts.Add(new First("Roxanne"));
return firsts;
}
}
MainWindow.xaml.cs
public partial class MainWindow : Window
{
private MainViewModel mvm;
public MainWindow()
{
var db = Database.GetChildren();
mvm = new MainViewModel(db);
InitializeComponent();
DataContext = mvm;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//Do not do this, example only
var f = new First("Billy");
mvm.AddFirstChild(new FirstViewModel(f));
//Prove that the event was raised in First, FirstViewModel see & handles it, and
//the UI is updated
f.AddChild(new Second(int.MaxValue));
}
}
MainWindow.xaml
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
Title="MainWindow">
<Grid>
<TreeView ItemsSource="{Binding Items}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:FirstViewModel}"
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding FirstName}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:SecondViewModel}">
<TextBlock Text="{Binding SecondNumber}" />
</DataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded"
Value="{Binding IsExpanded, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
<StackPanel Orientation="Vertical"
VerticalAlignment="Bottom">
<StackPanel Orientation="Horizontal">
<Button Content="Add Child to first First"
Command="{Binding AddFirstFirstChildCommand}" />
<Button Content="Toggle Expand"
Command="{Binding ToggleExpandCollapseCommand}" />
<Button Content="Add Child to second First"
Command="{Binding AddSecondFirstChildCommand}" />
</StackPanel>
<Button Content="Bad Codebehind Button"
Click="Button_Click"/>
</StackPanel>
</Grid>
</Window>
this returns array of Level1 from first table in DataSet (usually there's only one table)
public void Level1[] GetLevels()
{
DataSet ds = ....
return ds.Tables[0].Rows
.Select(row => new Level1((string)row["name"]))
.ToArray();
}
if you had more than one table in the dataset, you can use this method to loop trough all tables:
public void Level1[] GetLevels()
{
DataSet ds = ....
return ds.Tables
.SelectMany(t => t.Rows)
.Select(row => new Level1((string)row["name"]))
.ToArray();
}
The second code sample does exactly the same as your code in the question.
Understanding linq is extremely useful.

Categories

Resources