I want to populate my combobox2 after combobox1 selection changed event.
Here's some part of my XAML:
<ComboBox Name="cmbWorkcode"
ItemsSource="{Binding Workcodes}"
DisplayMemberPath="WorkcodeName"
SelectedValuePath="WorkcodeID"
SelectedValue="{Binding Path=WorkcodeId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<ComboBox Name="cmbProcess"
ItemsSource="{Binding Processes}"
DisplayMemberPath="ProcessName" SelectedValuePath="ProcessId"
SelectedValue="{Binding Path=ProcessId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
Some part of my ViewModel:
class MainWindowViewModel : ObservableObject
{
private ObservableCollection<Workcode> _workcodes = new ObservableCollection<Workcode>();
public ObservableCollection<Workcode> Workcodes
{
get { return _workcodes; }
set
{
_workcodes = value;
OnPropertyChanged("Workcodes");
}
}
private int _workcodeId;
public int WorkcodeId
{
get { return _workcodeId; }
set
{
_workcodeId = value;
OnPropertyChanged("WorkcodeId");
}
}
private ObservableCollection<Process> _processes = new ObservableCollection<Process>();
public ObservableCollection<Process> Processes
{
get { return _processes; }
set
{
_processes = value;
OnPropertyChanged("Processes");
}
}
private int _processId;
public int ProcessId
{
get { return _processId; }
set
{
_processId = value;
OnPropertyChanged("ProcessId");
}
}
public MainWindowViewModel()
{
PopulateWorkcode();
}
private void PopulateWorkcode()
{
using (var db = new DBAccess())
{
db.ConnectionString = ConfigurationManager.ConnectionStrings["connString"].ConnectionString;
db.Query = #"SELECT workcodeId, workcode FROM workcode";
DataTable data = db.GetData();
if (data != null)
{
foreach (DataRow row in data.Rows)
{
int workcodeId = Convert.ToInt32(row["workcodeId"].ToString());
string workcodeName = row["workcode"].ToString();
_workcodes.Add(new Workcode(workcodeId, workcodeName));
}
}
}
}
private void PopulateProcess()
{
using (var db = new DBAccess())
{
db.ConnectionString = ConfigurationManager.ConnectionStrings["connString"].ConnectionString;
db.Query = #"SELECT ProcessId, ProcessName FROM `process` WHERE WorkcodeId = #workcodeId";
DataTable data = db.GetData(new[] {new MySqlParameter("#workcodeId", _workcodeId.ToString())});
if (data != null)
{
foreach (DataRow row in data.Rows)
{
int id = Convert.ToInt32(row["ProcessId"].ToString());
string name = row["ProcessName"].ToString();
_processes.Add(new Process(id, name));
}
}
}
}
}
My problem is I don't know where do I trigger my PopulateProcess() method so that my combobox2 will be populated base on the selection of combobox1. Thanks for all the time and help! :)
--EDIT--
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Workcode
{
public int WorkcodeId { get; set; }
public string WorkcodeName { get; set; }
public Workcode(int id, string name)
{
WorkcodeId = id;
WorkcodeName = name;
}
}
initially the second combobox is empty and on select of the first combobox changed just pupulate the process
private int _workcodeId;
public int WorkcodeId
{
get { return _workcodeId; }
set
{
_workcodeId = value;
OnPropertyChanged("WorkcodeId");
if(WorkcodeID>0) PopulateProcess();
}
}
I can understand you want to have the next combobox to fill with data based on the previous value. Since i don't have classes of your type, i will give a simple example,
class ItemListViewModel<T> : INotifyPropertyChanged where T : class
{
private T _item;
private ObservableCollection<T> _items;
public ItemListViewModel()
{
_items = new ObservableCollection<T>();
_item = null;
}
public void SetItems(IEnumerable<T> items)
{
Items = new ObservableCollection<T>(items);
SelectedItem = null;
}
public ObservableCollection<T> Items
{
get { return _items; }
private set
{
_items = value;
RaisePropertyChanged("Items");
}
}
public T SelectedItem
{
get { return _item; }
set
{
_item = value;
RaisePropertyChanged("SelectedItem");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then have the main viewmodel that will be bound to the DataContext of the view. Have the Load methods do what you want
class MyViewModel : INotifyPropertyChanged
{
public MyViewModel()
{
First = new ItemListViewModel<string>();
Second = new ItemListViewModel<string>();
Third = new ItemListViewModel<string>();
First.PropertyChanged += (s, e) => Update(e.PropertyName, First, Second, LoadSecond);
Second.PropertyChanged += (s, e) => Update(e.PropertyName, Second, Third, LoadThird);
LoadFirst();
}
public ItemListViewModel<string> First { get; set; }
public ItemListViewModel<string> Second { get; set; }
public ItemListViewModel<string> Third { get; set; }
private void LoadFirst()
{
First.SetItems(new List<string> { "One", "Two", "Three" });
}
private void LoadSecond()
{
Second.SetItems(new List<string> { "First", "Second", "Third" });
}
private void LoadThird()
{
Third.SetItems(new List<string> { "Firsty", "Secondly", "Thirdly" });
}
private void Update<T0, T1>(string propertyName, ItemListViewModel<T0> parent, ItemListViewModel<T1> child, Action loadAction)
where T0 : class
where T1 : class
{
if (propertyName == "SelectedItem")
{
if (parent.SelectedItem == null)
{
child.SetItems(Enumerable.Empty<T1>());
}
else
{
loadAction();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
In XAML,
<ComboBox ItemsSource="{Binding First.Items}" SelectedItem="{Binding First.SelectedItem}" />
<ComboBox ItemsSource="{Binding Second.Items}" SelectedItem="{Binding Second.SelectedItem}" />
<ComboBox ItemsSource="{Binding Third.Items}" SelectedItem="{Binding Third.SelectedItem}" />
The issue is here
<ComboBox Name="cmbWorkcode"
ItemsSource="{Binding Workcodes}"
DisplayMemberPath="WorkcodeName"
SelectedValuePath="WorkcodeId"
SelectedValue="{Binding Path=WorkcodeId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
It should be WorkcodeId instead of WorkcodeID. rest you can try as Nishanth replied
public int WorkcodeId
{
get { return _workcodeId; }
set
{
if(_workcodeId !=value)
{
_workcodeId = value;
OnPropertyChanged("WorkcodeId");
PopulateProcess();
}
}
}
Related
I struggle to refresh the data attached to the RADdatagrid. The view with the Datagrid shows the position of a team in a competition. Every time a new game is finished the view has to be updated automatically. The data is stored in a sqlite database and a simple sql query gets the positions from the database. A button in the view that calls the query method does the job but I want to update the grid directly after the finished game is stored. I implemented INotifyPropertyChanged but no result. I also tried to fire the button_click event but that also didn't bring me the result. I want to do this right but I am open to a quick and dirty solution. Here is a bit of my code:
<StackPanel Grid.Row="2" Orientation="Horizontal">
<TextBlock Grid.Row="2" Margin="40,0,0,5">Team Ranking</TextBlock>
<Button x:Name="RefreshViewButton" Command="{Binding Path=RefreshView}" Margin="40 0 0 0 " Click="RefreshViewButton_Click">Refresh</Button>
</StackPanel>
<StackPanel Grid.Row="6" Margin="0, 0, 50, 0" VerticalAlignment="Stretch">
<telerikGrid:RadDataGrid
AutoGenerateColumns="False"
x:Name="Datagrid"
BorderThickness="0"
ItemsSource="{x:Bind ViewModel.TeamResult, Mode=TwoWay}"
ColumnDataOperationsMode="Flyout"
GridLinesVisibility="None"
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.AlignRightWithPanel="True"
UserEditMode="None" >
<telerikGrid:RadDataGrid.Columns>
<telerikGrid:DataGridNumericalColumn PropertyName="Rank" Header="Rank" SizeMode="Auto"/>
<telerikGrid:DataGridTextColumn PropertyName="Team_Naam" Header="TeamNaam" SizeMode="Auto"/>
<telerikGrid:DataGridTextColumn PropertyName="WedstrijdPunten" Header="WP" CanUserEdit="False" CanUserFilter="False" CanUserGroup="False" CanUserReorder="False" CanUserResize="False" CanUserSort="False" SizeMode="Auto" />
<telerikGrid:DataGridTextColumn PropertyName="PuntenVoor" Header="GP" SizeMode="Auto"/>
</telerikGrid:RadDataGrid.Columns>
</telerikGrid:RadDataGrid>
</StackPanel>
This is my view:
public sealed partial class TeamTotals : Page
{
public TeamResultsViewModel ViewModel { get; set; } = new TeamResultsViewModel();
public TeamTotals()
{
DataContext = ViewModel;
this.InitializeComponent();
}
public void RefreshViewButton_Click(object sender, RoutedEventArgs e)
{
ViewModel.LoadTeamResultsData();
}
}
My ViewModel:
public class TeamResultsViewModel : TeamModel
{
//public List<TeamModel> TeamResult = new List<TeamModel>();
public ObservableCollection<TeamModel> TeamResult = new ObservableCollection<TeamModel>();
TeamModel tm = new TeamModel();
public TeamResultsViewModel()
{
LoadTeamResultsData();
tm.PropertyChanged += Tm_PropertyChanged;
}
private void Tm_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
throw new NotImplementedException();
}
public void LoadTeamResultsData()
{
int i = 0;
if(TeamResult != null)
{
TeamResult.Clear();
}
try
{
string sql = "Select Team_Naam, WedstrijdPunten, PuntenVoor, PuntenTegen FROM Team WHERE KlasseId = 1 " +
"ORDER BY WedstrijdPunten DESC, PuntenVoor DESC LIMIT 10;";
var resultaat = SqliteDataAccess.LoadData<TeamModel>(sql, new Dictionary<string, object>());
foreach (var x in resultaat)
{
TeamResult.Add(x);
x.Rank = i++;
}
}
catch (Exception ex)
{
var messagedialog2 = new MessageDialog($"{ex}");
_ = messagedialog2.ShowAsync();
}
return;
}
}
and the model:
public class TeamModel : INotifyPropertyChanged
{
private int _id;
public int Id
{
get { return _id; }
set { _id = value; }
}
private int _klasseId;
public int KlasseId
{
get { return _klasseId; }
set { _klasseId = value; }
}
private string _team_naam;
public string Team_Naam
{
get { return _team_naam; }
set { _team_naam = value; }
}
private int _coachId;
public int CoachId
{
get { return _coachId; }
set { _coachId = value; }
}
private int _clubId;
public int ClubId
{
get { return _clubId; }
set { _clubId = value; }
}
private int _puntenVoor;
public int PuntenVoor
{
get { return _puntenVoor; }
set { _puntenVoor = value; }
}
private int _puntenTegen;
public int PuntenTegen
{
get { return _puntenTegen; }
set { _puntenTegen = value; }
}
private int _wedstrijdPunten;
public int WedstrijdPunten
{
get { return _wedstrijdPunten; }
set
{
_wedstrijdPunten = value;
OnPropertyChanged("WedstrijdPunten");
}
}
private int _rank;
public int Rank
{
get { return _rank; }
set { _rank = value; }
}
public List<SpelerModel> TeamLeden { get; set; } = new List<SpelerModel>();
public string Standen => $"{Team_Naam} : {PuntenVoor}";
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public override string ToString()
{
return Standen;
}
}
I have two datagrid's in two different tabitems. The datagrid in first tabitem displays some specific data on app load and should get updated if the data changes, pretty simple. The second tabitem's datagrid should be empty on app load but when an item is selected/typed from the combo box then it should filter the data as per that combo box selected item and show on the filtered data on datagrid of second tabitem (maintaining MVVM pattern).
This is what I've tried:
XAML
<TabControl>
<TabItem Header="Pending Bills">
<DataGrid ItemsSource="{Binding FilteredBills}" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn
Header="Vendor"
Binding="{Binding Path=Party, Mode=OneWay}"
Width="275"
IsReadOnly="True" />
<DataGridTextColumn
Header="Bill No."
Binding="{Binding Path=BillNo, Mode=OneWay}"
Width="275"
IsReadOnly="True" />
......
</DataGrid.Columns>
</DataGrid>
</TabItem>
<TabItem Header="Filtered Bills">
........
<DataGrid Grid.Row="0" ItemsSource="{Binding AllBills}" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn
Header="Vendor"
Binding="{Binding Path=Party, Mode=OneWay}"
Width="275"
IsReadOnly="True" />
<DataGridTextColumn
Header="Bill No."
Binding="{Binding Path=BillNo, Mode=OneWay}"
Width="275"
IsReadOnly="True" />
......
</DataGrid.Columns>
</DataGrid>
<StackPanel Orientation="Horizontal">
<ComboBox x:Name="cmbBox" ItemsSource="{Binding ComboItems}" SelectedItem="{Binding SelectedCBItem}"/>
</StackPanel>
</Grid>
</TabItem>
</TabControl>
A base viewmodel to enable the PropertyChangedEventHandler
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Basic model:
public class Bills : ViewModelBase
{
private int _id;
public int Id
{
get
{
return _id;
}
set
{
_id = value;
OnPropertyChanged("Id");
}
}
private string _party;
public string Party
{
get
{
return _party;
}
set
{
_party = value;
OnPropertyChanged("Party");
}
}
private string _billNo;
public string BillNo
{
get
{
return _billNo;
}
set
{
_billNo = value;
OnPropertyChanged("BillNo");
}
}
.......
}
The billsviewmodel and a helper data access class called Model:
public class BillsViewModel : ViewModelBase
{
public BillsViewModel()
{
cvsAll.Source = m.AllBills;
cvsFiltered.Source = m.FilteredBills;
}
Model m = new Model();
CollectionViewSource cvsAll = new CollectionViewSource();
public ICollectionView AllBills
{
get { return cvsAll.View; }
}
public List<string> ComboItems
{
get { return m.UniqueValues(); }
}
CollectionViewSource cvsFiltered = new CollectionViewSource();
public ICollectionView FilteredBills
{
get { return cvsFiltered.View; }
}
private string _selectedCBItem;
public string SelectedCBItem
{
get { return _selectedCBItem; }
set
{
_selectedCBItem = value;
cvsFiltered.Source = m.GetBills_Pen(value);
OnPropertyChanged("SelectedCBItem");
}
}
}
public class Model
{
private List<Bills> _allBills;
public List<Bills> AllBills
{
get
{
if (_allBills == null) LoadAllBillsFromDatabase();
return _allBills;
}
}
private List<Bills> _filteredBills;
public List<Bills> FilteredBills
{
get
{
if (_filteredBills == null) LoadUnpaidBillsFromDatabase();
return _filteredBills;
}
}
public void LoadAllBillsFromDatabase()
{
SQLiteConnection m_dbConnection = new SQLiteConnection(#"Data Source=Bills.db;");
SQLiteCommand sqlCom = new SQLiteCommand(#"Select * From billdata", m_dbConnection);
SQLiteDataAdapter sda = new SQLiteDataAdapter(sqlCom);
DataTable dt = new DataTable();
sda.Fill(dt);
_allBills = new List<Bills>();
foreach (DataRow row in dt.Rows)
{
_allBills.Add(new Bills()
{
Id = Convert.ToInt32(row["Id"]),
Party = (string)row["Party"],
BillNo = (string)row["BillNo"],
.......
});
}
m_dbConnection.Close();
}
public void LoadUnpaidBillsFromDatabase()
{
SQLiteConnection m_dbConnection = new SQLiteConnection(#"Data Source=Bills.db;");
SQLiteCommand sqlCom = new SQLiteCommand(#"Select * From billdata where PaidOn != ''", m_dbConnection);
SQLiteDataAdapter sda = new SQLiteDataAdapter(sqlCom);
DataTable dt = new DataTable();
sda.Fill(dt);
_filteredBills = new List<Bills>();
foreach (DataRow row in dt.Rows)
{
_filteredBills.Add(new Bills()
{
Id = Convert.ToInt32(row["Id"]),
Party = (string)row["Party"],
BillNo = (string)row["BillNo"],
.......
});
}
m_dbConnection.Close();
}
public List<string> UniqueValues()
{
return _allBills.Select((s) => s.Party).Distinct().OrderBy((s) => s).ToList();
}
public List<Bills> GetBills_Pen(string filterValue)
{
return _allBills.FindAll(s => s.Party == filterValue).ToList();
}
}
There are 2 issues:
The datagrid on 2nd tabitem is displaying all data on app load
When I select or type a valid item in the combo box the datagrid on 2nd tabitem is not showing the filtered data.
Have I not implemented the ICollectionView the right way or what am I doing wrong here and most importantly how do I fix this ?
This should work. I also added a function to ViewModelBase to make the code shorter
public class ViewModelBase
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void SetValue<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
field = value;
OnPropertyChanged(propertyName);
}
}
public class BillsViewModel : ViewModelBase
{
private List<BillViewModel> _allBills;
public BillsViewModel()
{
_allBills = m.AllBills;
AllBills = new ListCollectionView(_allBills);
ComboItems = _allBills.Select(b => b.Party).Distinct().OrderBy(b => b).ToList();
FilteredBills = new ListCollectionView(_allBills)
{
Filter = o => ((BillViewModel)o).Party == SelectedCBItem
};
}
Model m = new Model();
public ICollectionView AllBills { get; private set; }
public ICollectionView FilteredBills { get; private set; }
public List<string> ComboItems { get; private set; }
private string _selectedCBItem;
public string SelectedCBItem
{
get { return _selectedCBItem; }
set
{
SetValue(ref _selectedCBItem, value);
FilteredBills.Refresh();
}
}
}
Using the approach shown by #Firo I've managed to successfully implement in my app(so I'm marking his answer as accepted) and here is what I ended up with(in case it helps anyone who might have similar issues)
public class Model
{
private List<Bills> _sBills;
public List<Bills> SBills
{
get
{
if (_sBills == null) LoadAllBillsFromDatabase();
return _sBills;
}
}
public List<Bills> FilteredBills { get; private set; }
public void LoadAllBillsFromDatabase()
{
SQLiteConnection m_dbConnection = new SQLiteConnection(#"Data Source=Bills.db;");
SQLiteCommand sqlCom = new SQLiteCommand(#"Select * From billdata", m_dbConnection);
SQLiteDataAdapter sda = new SQLiteDataAdapter(sqlCom);
DataTable dt = new DataTable();
sda.Fill(dt);
_sBills = new List<Bills>();
foreach (DataRow row in dt.Rows)
{
_sBills.Add(new Bills()
{
Id = Convert.ToInt32(row["Id"]),
Party = (string)row["Party"],
BillNo = (string)row["BillNo"],
BillDt = (string)(row["BillDt"]),
Amt = (string)row["Amt"],
DueDt = (string)(row["DueDt"]),
PaidOn = (string)(row["PaidOn"])
});
}
m_dbConnection.Close();
}
}
public class BillsViewModel : ViewModelBase
{
private List<Bills> _allBills;
public BillsViewModel()
{
_allBills = m.SBills;
AllBills = new ListCollectionView(_allBills)
{
Filter = o => ((Bills)o).PaidOn==""
};
ComboItems = _allBills.Select(b => b.Party).Distinct().OrderBy(b => b).ToList();
FilteredBills = new ListCollectionView(_allBills)
{
Filter = o => ((Bills)o).Party == SelectedCBItem && ((Bills)o).PaidOn==""
};
}
Model m = new Model();
public ICollectionView AllBills { get; private set; }
public ICollectionView FilteredBills { get; private set; }
public List<string> ComboItems { get; private set; }
private string _selectedCBItem;
public string SelectedCBItem
{
get { return _selectedCBItem; }
set
{
SetValue(ref _selectedCBItem, value);
FilteredBills.Refresh();
}
}
}
The itemsource in the datagrid in 1st tabitem is AllBills and itemsource in the datagrid in 2nd tabitem is FilteredBills
I have ComboBox:
<ComboBox ItemsSource="{Binding Path=MonthDaysList}" IsSynchronizedWithCurrentItem="True"/>
Here is method to generate MonthDaysList data:
public ObservableCollection<string> MonthDaysList { get; internal set; }
public void GetMonths() {
MonthDaysList = new ObservableCollection<string>();
foreach (var item in MyConceptItems) {
MonthDaysList.Add(item.DateColumn);
}}
ObservableCollection & Binding are working fine, but it's not displayed default/first item into ComobBox:
It's possible to resolve it without set up Name of ComboBox?
Define a string source property in the view model and bind the SelectedItem property of the ComboBox to this one:
<ComboBox ItemsSource="{Binding Path=MonthDaysList}" SelectedItem="{Binding SelectedMonthDay}"/>
Make sure that you implement the INotifyPropertyChanged interface if you intend to set the source property dynamically:
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<string> _monthDaysList;
public ObservableCollection<string> MonthDaysList
{
get { return _monthDaysList; }
internal set { _monthDaysList = value; OnPropertyChanged(); }
}
private string _selectedMonthDay;
public string SelectedMonthDay
{
get { return _selectedMonthDay; }
internal set { _selectedMonthDay = value; OnPropertyChanged(); }
}
public void GetMonths()
{
MonthDaysList = new ObservableCollection<string>();
if (MyConceptItems != null && MyConceptItems.Any())
{
foreach (var item in MyConceptItems)
{
MonthDaysList.Add(item.DateColumn);
}
SelectedMonthDay = MonthDaysList[0];
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I'm new to WPF and I can't get my grid to auto-refresh when some property changes.
Only thing I achieved - auto-refresh on element adding.
Here is my code:
public partial class MainWindow : Window
{
private Model model;
public MainWindow()
{
InitializeComponent();
model = new Model();
MyGrid.ItemsSource = model.Content;
}
private void MyButton_Click(object sender, RoutedEventArgs e)
{
MyGrid.Items.Refresh();
}
}
public class Model
{
public ObservableCollection<Single> Content;
private Random r;
private Action action;
private static object _syncLock = new object();
public Model()
{
Content = new ObservableCollection<Single>();
r = new Random();
action = new Action(process);
action.BeginInvoke(null,null);
BindingOperations.EnableCollectionSynchronization(Content, _syncLock);
}
private void process()
{
while (true)
{
Content.Add(new Single { Name = "name" });
Content[r.Next(0,Content.Count())].Name = "rename" + r.Next(1,100);
Thread.Sleep(1000);
}
}
}
public class Single : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
RaisePropertyChanged(Name);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Make you model variable notifyable and this will work for you.
Try Changing the above code with.
public partial class Window1 : Window, INotifyPropertyChanged
{
private Model model;
public Model ModelData
{
get { return model; }
set
{
model = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("ModelData"));
}
}
public Window1()
{
this.InitializeComponent();
ModelData = new Model();
MyGrid.ItemsSource = ModelData.Content;
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class Model
{
public ObservableCollection<Single> Content { get; set; }
private Random r;
private static object _syncLock = new object();
public Model()
{
Content = new ObservableCollection<Single>();
Content.Add(new Single { Name = "name" });
r = new Random();
// BindingOperations.EnableCollectionSynchronization(Content, _syncLock);
DispatcherTimer t = new DispatcherTimer();
t.Interval = new TimeSpan(2000);
t.Tick += new EventHandler(t_Tick);
t.Start();
}
void t_Tick(object sender, EventArgs e)
{
App.Current.Dispatcher.BeginInvoke(new Action(() =>
{
if (Content.Count <= 100)
Content.Add(new Single { Name = "name" });
Content[r.Next(0, Content.Count())].Name = "rename" + r.Next(1, 100);
}));
}
}
public class Single : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
RaisePropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Xaml
<DataGrid Name="MyGrid" AutoGenerateColumns="False" Margin="246,175,0,0">
<DataGrid.Columns>
<DataGridTextColumn Header="Names" Binding="{Binding Name }" />
</DataGrid.Columns>
</DataGrid>
The problem was here:
public string Name
{
get { return name; }
set
{
name = value;
RaisePropertyChanged(Name);
}
}
Had to use
RaisePropertyChanged("Name");
Simple and stupid..
Update
Managed to fix the selectedIndex problem. I'd forgotten to set SelectedItem as well and naturally that caused a few issues.
So at 9AM this morning we got our 24 hour assignment and I have hit a brick wall.
We're supposed to create a program that allows a supervisor to Add and delete Employees and add Working Sessions, total hours and total earnings. But I am having some problems succesfully implementing this following the MVVM-Pattern. For some reason my Bindings simply aren't working and the only Solution I can see is someone looking over my project and helping me troubleshoot it.
Here is my code - I'm very sorry about having to post the entire thing but given that I have no clue where the problem is I did not see any other options. :
EmployeeModel
[Serializable]
public class WorkSessions : ObservableCollection<WorkSessionModel>
{
public WorkSessions()
{
}
}
[Serializable]
public class WorkSessionModel : INotifyPropertyChanged
{
private DateTime _dateTime;
private string _id;
private double _hours;
public WorkSessionModel()
{
}
public DateTime DateTime
{
get { return _dateTime; }
set
{
_dateTime = value;
NotifyPropertyChanged("DateTime");
}
}
public string ID
{
get { return _id; }
set
{
_id = value;
NotifyPropertyChanged("ID");
}
}
public double Hours
{
get { return _hours; }
set
{
_hours = value;
NotifyPropertyChanged("Hours");
NotifyPropertyChanged("TotalHours");
}
}
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
WorkSessionModel
[Serializable]
public class WorkSessions : ObservableCollection<WorkSessionModel>
{
public WorkSessions()
{
}
}
[Serializable]
public class WorkSessionModel : INotifyPropertyChanged
{
private DateTime _dateTime;
private string _id;
private double _hours;
public WorkSessionModel()
{
}
public DateTime DateTime
{
get { return _dateTime; }
set
{
_dateTime = value;
NotifyPropertyChanged("DateTime");
}
}
public string ID
{
get { return _id; }
set
{
_id = value;
NotifyPropertyChanged("ID");
}
}
public double Hours
{
get { return _hours; }
set
{
_hours = value;
NotifyPropertyChanged("Hours");
NotifyPropertyChanged("TotalHours");
}
}
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
EmployeeViewModel
public class EmployeeViewModel : ViewModelBase
{
private Employees _employeesModel = new Employees();
public Employees EmployeesView = new Employees();
public ObservableCollection<WorkSessionModel> WorkSessions { get; set; }
private string _id = "0";
private string _name = "noname";
private double _wage = 0;
private int _totalhours = 0;
public string ID
{
get { return _id; }
set { _id = value; RaisePropertyChanged("ID"); }
}
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged("Name");
}
}
public double Wage
{
get { return _wage; }
set
{
_wage = value;
RaisePropertyChanged("Wage");
}
}
public int TotalHours
{
get { return _totalhours; }
set
{
_totalhours = value;
RaisePropertyChanged("TotalHours");
}
}
private EmployeeModel _selectedEmployee = new EmployeeModel();
public EmployeeModel SelectedEmployee
{
get { return _selectedEmployee; }
set
{
_selectedEmployee = value;
RaisePropertyChanged("SelectedEmployee");
}
}
private int _selectedEmployeeIndex;
public int SelectedEmployeeIndex
{
get { return _selectedEmployeeIndex; }
set
{
_selectedEmployeeIndex = value;
RaisePropertyChanged("SelectedEmployeeIndex");
}
}
#region RelayCommands
// Employee Relay Commands
public RelayCommand EmployeeAddNewCommand { set; get; }
public RelayCommand EmployeeDeleteCommand { set; get; }
public RelayCommand EmployeeNextCommand { set; get; }
public RelayCommand EmployeePrevCommand { set; get; }
public RelayCommand EmployeeTotalHoursCommand { get; set; }
#endregion
public EmployeeViewModel()
{
InitCommands();
}
private void InitCommands()
{
EmployeeAddNewCommand = new RelayCommand(EmployeeAddNewExecute, EmployeeAddNewCanExecute);
EmployeeDeleteCommand = new RelayCommand(EmployeeDeleteNewExecute, EmployeeDeleteCanExecute);
EmployeeNextCommand = new RelayCommand(EmployeeNextExecute, EmployeeNextCanExecute);
EmployeePrevCommand = new RelayCommand(EmployeePrevExecute, EmployeePrevCanExecute);
//EmployeeTotalHoursCommand = new RelayCommand(EmployeeTotalHoursExecute, EmployeeTotalHoursCanExecute);
}
//private void EmployeeTotalHoursExecute()
//{
// _selectedEmployee.TotalHours();
//}
//private bool EmployeeTotalHoursCanExecute()
//{
// return true;
//}
private void EmployeeAddNewExecute()
{
EmployeeModel newEmployee = new EmployeeModel();
EmployeesView.Add(newEmployee);
_employeesModel.Add(newEmployee);
SelectedEmployee = newEmployee;
}
private bool EmployeeAddNewCanExecute()
{
return true;
}
private void EmployeeDeleteNewExecute()
{
if (MessageBox.Show("You are about delete all submissions for Employee," + SelectedEmployee.Name + "(" + SelectedEmployee.ID +")\r\nAre you sure?", "This is a Warning!", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
_employeesModel.Remove(SelectedEmployee);
EmployeesView.Remove(SelectedEmployee);
}
}
private bool EmployeeDeleteCanExecute()
{
if (SelectedEmployee != null)
return true;
else return false;
}
private void EmployeeNextExecute()
{
SelectedEmployeeIndex++;
}
private bool EmployeeNextCanExecute()
{
if (SelectedEmployeeIndex < EmployeesView.Count - 1)
return true;
return false;
}
private void EmployeePrevExecute()
{
SelectedEmployeeIndex--;
}
private bool EmployeePrevCanExecute()
{
if (SelectedEmployeeIndex > 0)
return true;
return false;
}
}
View
public partial class MainWindow : Window
{
public EmployeeViewModel EmployeeViewModel = new EmployeeViewModel();
public MainWindow()
{
InitializeComponent();
menu_employee.DataContext = EmployeeViewModel;
sp_employees.DataContext = EmployeeViewModel;
datagrid_employees.ItemsSource = EmployeeViewModel.EmployeesView;
grid_selectedEmployee.DataContext = EmployeeViewModel.SelectedEmployee;
}
}
I can see a few problems with your code:
When the SelectedIndex is updated, SelectedItem remains the same and vice versa.
It looks like your data binding is out of order:
The DataContext property cascades down to every child of a certain dependency object.
The code in the MainWindow constructor should probably be replaced by:
this.DataContext = EmployeeViewModel;
Then in XAML set the rest of the properties using Data Binding. The problem in your situation is that the DataContext of the selectedemployee is only set once. This means if you select another employee, then it will not update.
An example for your SelectedEmployee grid:
<Grid Name="grid_selectedEmployee" DataContext="{Binding SelectedEmployee,
UpdateSourceTrigger=PropertyChanged}">...</Grid>
One of the biggest things I see is you are setting properties, not binding them.
For example,
datagrid_employees.ItemsSource = EmployeeViewModel.EmployeesView;
You are telling your DataGrid that it's ItemsSource should be that specific object. You need to bind it to that value so you are telling it to point to that property instead. This will make your UI correctly reflect what's in your ViewModel
The other huge red flag I see is your ViewModel referencing something called and EmployeeView which leads me to believe your View and ViewModel too closely tied together.
Your ViewModel should contain all your business logic and code, while the View is usually XAML and simply reflects the ViewModel in a user-friendly way.
The View and the ViewModel should never directly reference each other (I have had my View reference my ViewModel in some rare occasions, but never the other way around)
For example, an EmployeesViewModel might contain
ObservableCollection<Employee> Employees
Employee SelectedEmployee
ICommand AddEmployeeCommand
ICommand DeleteEmployeeCommand
while your View (XAML) might look like this:
<StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="Add" Command="{Binding AddEmployeeCommand}" />
<Button Content="Delete" Command="{Binding DeleteEmployeeCommand}" />
</StackPanel>
<DataGrid ItemsSource="{Binding Employees}"
SelectedItem="{Binding SelectedEmployee}">
... etc
</DataGrid>
<UniformGrid DataContext="{Binding SelectedEmployee}" Columns="2" Rows="4">
<TextBlock Text="ID" />
<TextBox Text="{Binding Id}" />
... etc
</UniformGrid >
</StackPanel>
And the only thing you should be setting is the DataContext of the entire Window. Usually I overwrite App.OnStartup() to start up my application:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var view = new MainWindow();
var vm = new EmployeesViewModel;
view.DataContext = vm;
view.Show();
}
}
Although I suppose in your case this would also work:
public MainWindow()
{
InitializeComponent();
this.DataContext = new EmployeesViewModel();
}