ObservableCollection not always updating UI and variable change incorrectly - c#

I have the following ViewModel and in my code link this to an items control. However when I use a line such as:
onOffSchedule.dc.schedules[0].days[0].data[0] = 9;
it only updates the UI sometimes?? And when it does it updates the data all wrong. Instead of assign the first schedule, the first day, and first time slot to 9. It updates the first schedule (for all days??) for the first time slot.
So I am trying to figure out why it updates every index in the days array instead of just the first one.
Thanks in advance!
public class ScheduleVM
{
public ObservableCollection<Schedule> schedules { get; set; }
private static ScheduleVM viewModel = null;
public static ScheduleVM getInstance()
{
if (viewModel == null)
viewModel = new ScheduleVM();
return viewModel;
}
private ScheduleVM()
{
schedules = new ObservableCollection<Schedule>();
for (byte i = 0; i < 32; i++)
schedules.Add(new Schedule());
}
}
public class Schedule
{
public ObservableCollection<Day> days { get; set; }
public Schedule()
{
days = new ObservableCollection<Day>();
int[] values = new int[96];
for (byte i = 0; i < 96; i++)
values[i] = 3;
for (byte i = 0; i < 8; i++)
days.Add(new Day() { data = values });
}
}
public class Day : BaseVM
{
private int[] _data;
public int[] data
{
get
{
return _data;
}
set
{
_data = value;
OnPropertyChanged("data");
}
}
}
Below is the view that goes along with this code. It is a user control that I create inside another window (and inside that window the user control is called 'onOffSchedule'.
public ScheduleVM dc { get; private set; }
public Schedule()
{
InitializeComponent();
dc = ScheduleVM.getInstance();
this.DataContext = dc;
schedule.ItemsSource = dc.schedules[0].days;
}

onOffSchedule.dc.schedules[0].days[0].data[0] = 9;
That line will not trigger an update under normal circumstances. What you're doing is just setting an integer in an integer array:
public int[] data { ... }
You put the OnPropertyChanged call in there for when the entire array changes. But when you just set one of the values, WPF doesn't know that occurred.
You have the right idea by using the ObservableCollection<Day> on your other class. You need to do something similar in your Day class for the data property.

You're updating an element within the data array. That array does not fire off property changed notifications, so there's no way that the View knows to update itself. Make your data an ObservableCollection and see if it helps.

Related

Bind a List of Objects to Chart

I have a list of objects:
List<MyClass> MyList = new List<MyClass>();
the MyClass contains Properties Dtm and LastPrice that are updated real-time
public class MyClass
{
int dtm;
double lastPrice = 0;
public int Dtm
{
get { return dtm; }
set { dtm = value; }
}
public double LastPrice
{
get { return lastPrice; }
set { lastPrice = value; }
}
}
I want now a chart lined to the list that updates automatically each time the properties change. Any idea on how to do it?
Thanks
For an overview of Binding Data to Series (Chart Controls) see here!
The easiest way to bind your List<T> to a Chart is simply to set the List as DataSource and to set the X- and Y-Value members of a Series S1:
chart1.DataSource = MyList;
S1.XValueMember = "Dtm";
S1.YValueMembers = "LastPrice";
As for updating the chart you use the DataBind method:
chart1.DataBind();
Now you have two options:
Either you know when the values change; then you can simply add the call after the changes.
But maybe you don't know just when those changes occur or there are many actors that all may change the list.
For this you can add the DataBind call right into the setter of the relevant property:
public class MyClass
{
int dtm;
double lastPrice = 0;
public static Chart chart_ { get; set; }
public int Dtm
{
get { return dtm; }
set { dtm = value; }
}
public double LastPrice
{
get { return lastPrice; }
set { lastPrice = value; chart_.DataBind(); }
}
// a constructor to make life a little easier:
public MyClass(int dt, double lpr)
{ Dtm = dt; LastPrice = lpr; }
}
For this to work the List must learn about the chart to keep updated. I have added a reference to the class already. So before adding/binding points we set a chart reference:
MyClass.chart_ = chart1; // set the static chart to update
// a few test data using a Random object R:
for (int i = 0; i < 15; i++)
MyList.Add(new MyClass(R.Next(100) + 1 , R.Next(100) / 10f) );
The reference can be static, as all List elements will update the same chart.

How to set class values in an array of class

I am using Visual Studio to create a Windows Forms C# project and am trying to set up an array of a type class, and have the entries in the array correspond to the constructor string for the class. I am using an array with an index of a variable, which increases each time that a new class instance is added to the array.
I am running into the problem of the index call is outside the bounds of the array. Additionally, I am not sure that my class variables are being set for each instance. Can anyone point me in the right direction? Below is my code:
public partial class MainMenu : Form
{
//int that will be used to alter the index of the array
public static int acctcount = 1;
//array of class Account
Account[] accounts = new Account[acctcount];
public MainMenu()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
//check through each element of the array
for (int i = 0; i < accounts.Length; i++)
{
string stringToCheck = textBox1.Text;
foreach(Account x in accounts)
{
//check to see if entered name matches any element in the array
if (x.Name == stringToCheck)
{
//set variables in another form so that we are using the class variables for only that class
Variables1.selectedAccount = x.Name;
//is this calling the CheckBalance of the instance?
Variables1.selectedCheckBalance = Account.CheckBalance;
//same thing?
Variables1.selectedSaveBalance = Account.SaveBalance;
//switch to form
AccountMenu acctMenu = new AccountMenu();
this.Hide();
acctMenu.Show();
}
else
{
/*insert new instance of Account
the index element should be 0, since acctcount is set to 1
and we are subtracting 1 from the acctcount
we are using the string from the textbox1.Text as the constructor
for the new instance*/
accounts [acctcount-1] = new Account(stringToCheck);
//increase the index of the array by 1
acctcount += 1;
}
}
}
}
}
class Account
{
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
private static int acctNum = 0;
public static int AcctNumber
{
get
{
return acctNum;
}
set
{
acctNum = value;
}
}
//initialize the CheckBalance value to 100.00
private static decimal checkBalance = 100.00M;
public static decimal CheckBalance
{
get
{
return checkBalance;
}
set
{
checkBalance = value;
}
}
public Account(string Name)
{
this.Name = Name;
}
private static decimal saveBalance = 100.00M;
public static decimal SaveBalance
{
get
{
return saveBalance;
}
set
{
saveBalance = value;
}
}
}
The problem with the reported exception is [most likely] the line accounts[acctcount-1] as it will throw an IndexOutOfBounds exception when acctcount is >= 2 (eg. accounts[1]), as happens after the first button click and increment of acctcount. The array however, only has one element as it was created with accounts = new Account[acctcount]; - arrays in C# do not grow/resize.
The simplest and best immediate fix is to use a List (see Collections (C#)) instead of an array; Lists can grow dynamically. Then the code becomes:
// (remove the "acctcount" field as it is no longer useful)
List<Account> accounts = new List<Account>();
// ..
accounts.Add(new Account(stringToCheck));
As pointed out by Trevor, remove the static modifier in the Accounts class; otherwise the member data will be incorrectly shared (ie. each account will have the same balances!) and "overwrite" each other. If the use of static is an attempt to "pass back" the data from the form see How to return a value from a Form in C#? for a more viable solution. The same use of a public property can be used to pass an (Account) object into a form.
The exception is thrown when the button is clicked more than once.
You created an array of size 1, the second time you click the button and it tries to add an element at index 2, the index is already out of bounds.
Arrays do not grow in size as you add new items.
As pointed out, you should use a collection, like List<T>
If you wanted to keep using arrays, everytime you add a new item, you need to create a new array of bigger size, copy the elements of the old array to the new array, and reference the old array to the new array. You can also create an array of a bigger size and only create a new array when it's full. Which is basically what the .Net collections already implement.
As always, it all depends on what your needs and requirements are.

Change bound Property when programmatically changing SelectedIndex

I've set up a simple form. A ListBox takes values from a list in the 'business object' displaying the Name property and providing the Value property.
In additon the ListBox's SelectedItem property is bound to a property in the same business object.
Using the UI to select a value from the list correctly changes the objects property (checked when the button is clicked) and the correct value is available. So far so good.
However, if the ListBox's SelectedIndex property is changed in the code, then the UI correctly changes as expected but the business property does not change - it would appear to have missed the change event. This is true for both setting in the constructor and in the button event handler (see the code).
What have I missed or what am I doing incorrectly.
(I've only included the code I've written - not VS wizard generated stuff)
class Frequency
{
public String Name { get; set; }
public Int16 Value { get; set; }
public Frequency(String name, Int16 value)
{
Name = name;
Value = value;
}
}
class FrequencyList : System.ComponentModel.BindingList<Frequency>
{
}
class Model
{
public static FrequencyList FrequencyValues = new FrequencyList()
{
new Frequency("Slowest", 100),
new Frequency("Slow", 150),
new Frequency("Medium", 1000),
new Frequency("Fast", 5500),
new Frequency("Fastest", 10000)
};
public Frequency StartFrequency { get; set; }
public void DoStuff()
{
if (StartFrequency == null)
return;
Int16 freq = StartFrequency.Value;
}
}
public partial class Form1 : Form
{
private Model myModel = new Model();
public Form1()
{
InitializeComponent();
// Bind the list to a copy of the static model data
this.listBox1.DataSource = Model.FrequencyValues;
// Bind the control to the model value
this.listBox1.DataBindings.Add("SelectedItem", myModel, "StartFrequency");
// Select the start value
this.listBox1.SelectedIndex = 3;
}
private void button1_Click(object sender, EventArgs e)
{
Int16 f = (Int16)listBox1.SelectedValue;
this.myModel.DoStuff();
int new_index = listBox1.SelectedIndex + 1;
if (new_index >= listBox1.Items.Count)
new_index = 0;
listBox1.SelectedIndex = new_index;
}
}
You don't want the Click event, you want the SelectedIndexChanged event. This will trigger regardless of whether the user or the program instigates the change.

Implementing a custom sort for WinForms ListView

I have a class called Picture and that has a name and size (int) property.
I was going to sort them using size, but not the displayed file name which is the item name in the listview.
I implemented IComparer<Picture> for the Picture type, and then when I write this:
this.PicList.ListViewItemSorter = AllPictures[0];
or
this.PicList.ListViewItemSorter = Picture;
they don't compile.
How do I do this? On MSDN it shows a separate class for the sorter but can I do it built-in with the used type Picture?
you need to give it an actual instance of your Picture class, not the type. Also, your ListViewItemSorter will actually call the Comparer by passing it the ListViewItem not the Picture class, you can add an instance of the Picture class to the Tag property of your ListViewItem.
Something like this, this is a very rough implementation, just to give you an idea. You can implement your own error handling.
Picture test1 = new Picture() { Name = "Picture #1", Size = 54 };
Picture test2 = new Picture() { Name = "Picture #2", Size = 10 };
this.listView1.ListViewItemSorter = test1;
this.listView1.Items.Add(new ListViewItem(test1.Name) { Tag = test1 });
this.listView1.Items.Add(new ListViewItem(test2.Name) { Tag = test2 });
public class Picture : IComparer
{
public string Name { get; set; }
public int Size { get; set; }
#region IComparer Members
public int Compare(object x, object y)
{
Picture itemA = ((ListViewItem)x).Tag as Picture;
Picture itemB = ((ListViewItem)y).Tag as Picture;
if (itemA.Size < itemB.Size)
return -1;
if (itemA.Size > itemB.Size)
return 1;
if (itemA.Size == itemB.Size)
return 0;
return 0;
}
The output is:
Picture #2
Picture #1
Here is an MSDN Link
Another implementation you can try is to associate each ListViewItem index with your custom class/struct instance stored in a Dictionary<int, Picture> instance. Your custom list view sorter class could be written with that in mind like so:
public partial class Form1 : Form
{
public Form1()
{
...
ListView lv = new ListView();
lv.ListViewItemSorter = new MyCustomSorter(this);
}
private Dictionary<int, Picture> _pictures = new Dictionary<int,Picture>();
private class MyCustomSorter : IComparer
{
private Form1 _parent;
internal MyCustomSorter(Form1 form)
{
_parent = form;
}
#region IComparer Members
public int Compare(object x, object y)
{
ListViewItem item1 = x as ListViewItem;
ListViewItem item2 = y as ListViewItem;
if (x != null)
{
if (y != null)
{
Picture p1 = _parent._pictures[item1.Index];
Picture p2 = _parent._pictures[item2.Index];
return string.Compare(p1.Location, p2.Location);
}
// X is deemed "greater than" y
return 1;
}
else if (y != null)
return -1; // x is "less than" y
return 0;
}
#endregion
}
}
public class Picture
{
private string _location;
public string Location{
get { return _location; }
}
}
Stan's solution will work, but, on behalf of your users, I'd strongly suggest you don't hard code the sorting order. On a ListView, everyone expects to be able to sort by clicking on the header.
ObjectListView (an open source wrapper around .NET WinForms ListView) provides sort-on-click functionality for free. It could even show a thumbnail of your pictures in its own column if you want it to.

Formating DataGridView Columns for an unbound data source

I am struggling with the WinForms DataGridView. I have a class, that I use as element to be displayed:
public class BorderFlowHistoryElement
{
public string nodeTitles { get; set; }
public double borderFlowRatio { get; set; }
...
}
I created a list of these elements:
List<BorderFlowHistoryElement> clusterHistory
which contains a list of thise elements, that should be displayed in my DataGridView. I bound the list at the DataSource of the Grid:
dataGridViewCluster.DataSource = clusterHistory;
Now the DataGridView displays the list. Now I want to format the columns which display the double values to display 5 digits. I tried it with:
dataGridViewCluster.Columns[1].DefaultCellStyle.Format = "n5";
but this has no effect on the column. Anyone knows, how I can do it right?
Additionally, I want to size the columnwidth to optimal fit for the largest entry.
Thanks in advance,
Frank
I have replicated what you have done and I had no issue whatsoever. Have you validated your data to ensure that you can actually get the results that you want?
Here is what I did just for your reference:
private void button1_Click(object sender, EventArgs e)
{
IList<BorderFlowHistoryElement> clusterHistory = FillClusterHistory();
dataGridView1.DataSource = clusterHistory;
dataGridView1.Columns[1].DefaultCellStyle.Format = "n5";
dataGridView1.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells);
}
private static IList<BorderFlowHistoryElement> FillClusterHistory()
{
IList<BorderFlowHistoryElement> clusterHistory = new List<BorderFlowHistoryElement>();
for(int i = 5000; i < 5020; i++)
{
BorderFlowHistoryElement element = new BorderFlowHistoryElement();
element.nodeTitles = Guid.NewGuid().ToString();
element.borderFlowRatio = i * 3.3.1415672467234823499821D;
clusterHistory.Add(element);
}
return clusterHistory;
}
}
public class BorderFlowHistoryElement
{
private string _NodeTitles;
private double _BorderFlowRatio;
public string nodeTitles
{
get { return _NodeTitles; }
set { _NodeTitles = value;}
}
public double borderFlowRatio
{
get { return _BorderFlowRatio; }
set { _BorderFlowRatio = value;}
}
}
I hope that helps in some fashion. As you can see you can do the auto sizing as well.

Categories

Resources