I have this tiny demo that appears to show that ListView continually creates ViewCells, and binds them to the one view model, until it runs out of memory. Here's the code...
From MainPage.xaml:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="LVD.MainPage">
<StackLayout>
<Label Text="This is a listview with one item."/>
<Label Text="{Binding MainNum}"/>
<ListView x:Name="DemoList"
ItemsSource="{Binding Itms}"
RowHeight="75"
CachingStrategy="RecycleElement">
</ListView>
</StackLayout>
</ContentPage>
MainPage.xaml.cs:
public partial class MainPage : ContentPage
{
MainVM _vm = null;
public MainPage()
{
InitializeComponent();
_vm = new MainVM();
BindingContext = _vm;
DemoList.ItemTemplate = new DataTemplate(typeof(myCell));
}
}
MainVM.cs:
using System.Collections.ObjectModel;
namespace LVD
{
class MainVM
{
public string MainNum { get; set; }
private static int _mainserno = 0;
public ObservableCollection<CellVM> Itms { get; set; }
public MainVM()
{
MainNum = "Parent serial#: " + _mainserno.ToString();
_mainserno++;
if (Itms == null)
Itms = new ObservableCollection<CellVM>();
else
Itms.Clear();
CellVM item = new CellVM();
Itms.Add(item);
//CellVM item2 = new CellVM();
//Itms.Add(item2);
}
}
}
myCell.cs:
using Xamarin.Forms;
namespace LVD
{
class myCell : ViewCell
{
static int _localserialno = 0;
Label VMnumLbl, CellNumLbl, PLbl;
public static readonly BindableProperty ItemProperty =
BindableProperty.Create("CellItem", typeof(CellVM), typeof(myCell), null);
public CellVM CellItem
{
get { return (CellVM)GetValue(ItemProperty); }
set { SetValue(ItemProperty, value); }
}
public myCell()
{
VMnumLbl = new Label();
VMnumLbl.SetBinding(Label.TextProperty, "VMSerNum", BindingMode.Default, null, "VM Serial #: {0}");
CellNumLbl = new Label();
CellNumLbl.SetBinding(Label.TextProperty, "CellSerNum", BindingMode.Default, null, "Cell Serial #: {0}");
PLbl = new Label();
PLbl.SetBinding(Label.TextProperty, "PingCount", BindingMode.Default, null, "Pings: {0}");
var stackit = new StackLayout();
stackit.Children.Add(VMnumLbl);
stackit.Children.Add(CellNumLbl);
stackit.Children.Add(PLbl);
View = stackit;
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (BindingContext == null)
CellItem = null;
else if (BindingContext != CellItem)
{
CellItem = (CellVM)BindingContext;
CellItem.CellSerNum = _localserialno++;
CellItem.Ping();
}
}
}
}
CellVM.cs:
class CellVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private static int serialno = 0;
private int _vmserialno;
public int VMSerNum { get { return _vmserialno; } }
public int CellSerNum { get; set; }
public int PingCount { get; set; }
public CellVM()
{
_vmserialno = serialno++;
PingCount = 0;
}
public void Ping()
{
PingCount++;
OnPropertyChanged(string.Empty);
}
}
When I run this code, the display looks like this:
This is a listview with one item.
Parent serial #: 0
VM Serial #: 0
Cell Serial #: ++
Pings: ++
The bottom two numbers continually increase. That means the listview recreates the CellView, and binds them all to the same viewmodel. If I put two items in the list, both cells have running Cell Serial #'s and Pings.
Since I couldn't find anyone else complaining about this, I must be doing something wrong. Can anyone enlighten me to what that might be?
advTHANKSance (pun intended)
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 implemented horizontal list using collection view in Xamarin forms. The Underline doesn't come properly it will be late while selecting Item. The Refreshing is too late. AS you can see in the following Video
My Xaml Code
<CollectionView
x:Name="rooms_List"
IsEnabled="True"
SelectedItem="{Binding SelectedRoom}"
SelectionChangedCommand="{Binding Source={x:Reference ThePage}, Path= BindingContext.RoomChanged}"
ItemsLayout = "HorizontalList"
SelectionChanged="RoomCollectionSelectionChanged"
BackgroundColor = "white"
HeightRequest="50"
SelectionMode="Single"
HorizontalScrollBarVisibility="Never"
ItemsSource="{Binding RoomList}">
<CollectionView.ItemTemplate >
<DataTemplate>
<Grid>
<StackLayout VerticalOptions="Start" Orientation="Vertical">
<Label Text ="{Binding RoomName}" Padding="20,10,20,0" />
<BoxView x:Name="line" HeightRequest="3" IsVisible="{Binding IsSelected}" BackgroundColor="#1484B8" WidthRequest="5" Margin="18,0,15,0" />
</StackLayout>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
My Xaml.cs RoomCollectionSelectionChanged
private void RoomCollectionSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.CurrentSelection.Count == 0)
{
room_image.IsVisible = true;
}
else
{
var selectedItem = e.CurrentSelection.FirstOrDefault() as Room;
selectedRoom = selectedItem.RoomName;
if (selectedRoom == "All")
{
room_image.IsVisible = false;
}
else if (e.PreviousSelection.Count == 1)
{
var previousItem = (e.PreviousSelection.FirstOrDefault() as Room)?.RoomName;
if (previousItem != "")
{
room_image.IsVisible = true;
room_image.Source = selectedItem.RoomImage;
}
}
else
{
room_image.IsVisible = true;
room_image.Source = selectedItem.RoomImage;
}
}
}
My ViewModel
private ObservableCollection<Room> roomList { get; set; }
public ObservableCollection<Room> RoomList
{
get { return roomList; }
set
{
roomList = value;
OnPropertyChanged(nameof(RoomList));
}
}
private Room selectedRoom { get; set; }
public Room SelectedRoom
{
get { return selectedRoom; }
set
{
selectedRoom = value;
}
}
public bool isSelected { get; set; }
public bool IsSelected
{
get { return isSelected; }
set
{
if (value != isSelected)
{
isSelected = value;
OnPropertyChanged(nameof(IsSelected));
}
}
}
private Room previousSelectedRoom;
private void SelectedRoomEvent()
{
if (SelectedRoom != null)
{
DevicePage.checkRoom = true;
string RoomName = SelectedRoom.RoomName;
if (RoomName.Equals("All"))
{
GetDeviceAndRoomData();
}
else
{
int RoomId = SelectedRoom.RoomId;
if (previousSelectedRoom != null)
{
previousSelectedRoom.IsSelected = false;
}
previousSelectedRoom = SelectedRoom;
previousSelectedRoom.IsSelected = true;
}
}
My Model
public class Room
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
[JsonProperty("roomId")]
public int RoomId { get; set; }
[JsonProperty("serialNumber")]
public string SerialNumber { get; set; }
[JsonProperty("roomName")]
public string RoomName { get; set; }
[JsonProperty("roomImage")]
public string RoomImage { get; set; }
[JsonIgnore]
public bool IsSelected { get; set; }
}
Please give suggestions how to fix this enter image description here
I see you use e. PreviousSelection in your code. That is better than what I was doing in my repository, so I have modified github ToolmakerSteve - repo XFIOSHorizCollViewScrollBug to use that.
I made changes in several places in this file:
using System.Collections.ObjectModel;
using System.Linq;
using Xamarin.Forms;
namespace XFIOSHorizCollViewScrollBug
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
InitRoomList();
BindingContext = this;
// After set BindingContext, so RoomCollectionSelectionChanged gets called.
var room = RoomList[0];
rooms_List.SelectedItem = room;
}
public ObservableCollection<Room> RoomList { get; set; }
private void RoomCollectionSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var previousItem = e.PreviousSelection.FirstOrDefault() as Room;
DeselectRoom(previousItem);
var selectedItem = e.CurrentSelection.FirstOrDefault() as Room;
SelectRoom(selectedItem);
}
private void DeselectRoom(Room room)
{
if (room != null)
{
room.IsSelected = false;
}
}
private void SelectRoom(Room room)
{
if (room != null) {
room.IsSelected = true;
rooms_List.ScrollTo(room, position: ScrollToPosition.Center, animate: false);
}
}
string[] roomNames = new string[] {
"One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight"
};
private void InitRoomList()
{
var rooms = new ObservableCollection<Room>();
foreach (var name in roomNames) {
rooms.Add(new Room(name));
}
RoomList = rooms;
}
}
}
The other files are the same as in my answer to your previous question.
Please download my repository, build and run it, verify that it works. The change to underline happens INSTANTLY - as soon as you click on an item.
Then compare that to your code. Change your code until it matches what the working repo does.
I have been reading documentation for several days now but I can't get it working, no matter what I try. I have Basic Row chart and want to display as a graph time spent. My bar title and value are changing constantly (more items getting added). I am able to add bars with my current code, but I am not able to add title for each added bar. Only first title / first bar title is visible, all the others / coming are not visible.
How to add title and value in a proper way? (I am already familiar with documentation https://lvcharts.net/App/examples/v1/wf/Basic%20Row)
Here is my code (you can see from commented out sections what has been tried yet):
public static SeriesCollection SeriesCollection { get; set; }
public static string[] Labels { get; set; }
public static List<string> LabelsList { get; set; }
public static Func<double, string> Formatter { get; set; }
public AppUsageBarGraph()
{
InitializeComponent();
LabelsList = new List<string>();
SeriesCollection = new SeriesCollection
{
new RowSeries
{
Values = new ChartValues<double> { },
DataLabels = true
}
};
DataContext = this;
}
public static void UpdateChart()
{
SeriesCollection[0].Values.Clear();
LabelsList.Clear();
//Labels = MainProcess.ActivityLogGrouped.Rows.Cast<DataRow>().Select(row => row["Window Title"].ToString()).ToArray();
foreach (DataRow row in MainProcess.ActivityLogGrouped.Rows)
{
SeriesCollection[0].Values.Add(Convert.ToDouble(row["Time Spent"]));
//SeriesCollection[0]. = row["Time Spent"].ToString());
LabelsList.Add(row["Window Title"].ToString());
}
//MessageBox.Show(Labels[0].ToString());
Labels = LabelsList.ToArray();
//foreach (var item in Labels)
//{
// MessageBox.Show(item);
//}
//Labels = new[]
// {
// "Shea Ferriera",
// "Maurita Powel",
// "Scottie Brogdon",
// "Teresa Kerman",
// "Nell Venuti",
// "Anibal Brothers",
// "Anderson Dillman"
// };
//Formatter = value => value.ToString("N");
}
The key is to use a ObservableCollection<string> instead of a string[].
I also recommend to use a model to encapsulate the actual chart data points. I introduced the class DataModel for this reason.
The following example shows how to dynamically bind values and labels to the chart. I should say that making everything public static is a very bad smelling code design.
MainWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<wpf:CartesianChart Height="500">
<CartesianChart.Series>
<RowSeries Values="{Binding ChartModel.RowSeries}"
Configuration="{Binding ChartModel.RowSeriesConfiguration}"/>
</CartesianChart.Series>
<CartesianChart.AxisY>
<Axis Labels="{Binding ChartModel.RowSeriesLabels}" />
</CartesianChart.AxisY>
</CartesianChart>
</Window>
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
this.ChartModel = new ChartModel();
}
public void UpdateChart()
{
foreach (DataRow row in MainProcess.ActivityLogGrouped.Rows)
{
if (double.TryParse(row["Time Spent"], out double value)
{
string label = row["Window Title"];
var newDataModel = new DataModel(value, label);
this.ChartModel.RowSeries.Add(newDataModel);
this.ChartModel.RowSeriesLabels.Add(newDataModel.Label);
}
}
}
public ChartModel ChartModel { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ChartModel.cs
public class ChartModel : INotifyPropertyChanged
{
public ChartModel()
{
// Initialize chart
this.RowSeries = new ChartValues<DataModel>()
{
new DataModel(20, "Shea Ferriera"),
new DataModel(100, "Maurita Powel"),
new DataModel(60, "Scottie Brogdon"),
};
// Create labels
this.RowSeriesLabels = new ObservableCollection<string>();
foreach (DataModel dataModel in this.RowSeries)
{
this.RowSeriesLabels.Add("dataModel.Label");
}
// DatModel to value mapping
this.RowSeriesConfiguration = new CartesianMapper<DataModel>()
.X(dataModel => dataModel.Value);
}
public CartesianMapper<DataModel> RowSeriesConfiguration { get; set; }
public ChartValues<DataModel> RowSeries { get; set; }
public ObservableCollection<string> RowSeriesLabels { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
DataModel.cs
public class DataModel
{
public DataModel(double value, string label)
{
this.Value = value;
this.Label = label;
}
public double Value { get; set; }
public string Label { get; set; }
}
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();
}
}
}
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();
}