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.
Related
I have a datagrid that contains mostly DataGridViewComboBoxColumn columns as data. The number of columns in the DataGridView varies based on the type of
product being listed. To make editing of each row of the data easier I have created a modal editor to display all the columns as multiple rows and columns
(need to remove the horizontal scrolling forced on the user by the data grid). The data in one row of the datagrid needs to be transferred to the modal editor.
Obviously the controls in the modal editor will not be of type DataGridViewComboBoxCell. They are currently of type ListBox, but in most of
the cases this is easy enough to change to ComboBox.
The controls in the modal editor need to be initiated with the currently
selected values of the DataGridViewComboBox Cells if the user has previously
edited the control.
The way I originally planed to do it was to get the selected index of the
DataGridViewComboBox but that doesn't seem to exist. Is there a method to get
the selected index that does not involve comparing the Cell Value to each of
the items in the DataGridViewComboBoxColumn.items list?
This question refers to the second constructor where it is looping through the list of items to get selected index.
public class ControlTransferData
{
private List<string> _valueStrings;
private string _name;
private bool _hasSelection;
private int _indexSelected;
public ControlTransferData()
{
_valueStrings = new List<string>();
_name = null;
_indexSelected = -1;
_hasSelection = false;
Width = 0;
Height = 0;
}
public ControlTransferData(DataGridViewComboBoxColumn CboxCol, DataGridViewCell currentControlToTransfer)
{
_valueStrings = new List<string>();
foreach (string item in CboxCol.Items)
{
string itemStringValue = item.ToString();
if (!string.IsNullOrWhiteSpace(itemStringValue) && !string.IsNullOrEmpty(itemStringValue))
{
_valueStrings.Add(itemStringValue);
}
}
if (currentControlToTransfer.Value != null)
{
_hasSelection = true;
int selectedIndex = 0;
string selectedItem = currentControlToTransfer.Value.ToString();
foreach (string currentString in _valueStrings)
{
if (currentString.CompareTo(selectedItem) == 0)
{
_indexSelected = selectedIndex;
}
selectedIndex++;
}
}
else
{
_indexSelected = -1;
_hasSelection = false;
}
_name = CboxCol.HeaderText;
Width = 0;
Height = 0;
}
public string Name { set { _name = value } get { return _name; } }
public List<string> ValueStrings { set { _valueStrings = value; } get { return _valueStrings; } }
public bool HasSelection { set { _hasSelection = value; } get { return _hasSelection; } }
public int IndexSelected { set { _indexSelected = value; } get { return _indexSelected; } }
public int Width { set; get; }
public int Height { set; get; }
}
you can use below code:
myDGV.Columns["SampleColumn"].Index;
This would work in winForms.
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.
I have a class named Result which has 4 fields as id, marks, total, avg. I created the List class and stored the result.
Now I want to display only 2 columns in the datagrid. They are id and total. I successfully displayed the id and total but the datagrid shows 4 columns instead of 2. The columns are id, total, id, total
Here is my code to display the datagrid:
private void Form1_Load(object sender, EventArgs e)
{
List<Result> result = new List<Result>();
PopulateResult();
dgresult.AutoGenerateColumns = false;
dgresult.Items.Clear();
dgresult.ItemsSource = result;
DataGridTextColumn col1 = new DataGridTextColumn();
col1.Header = "Id";
col1.Binding = new Binding("Id");
DataGridTextColumn col2 = new DataGridTextColumn();
col2.Header = "Total";
col2.Binding = new Binding("Total");
dgresult.Columns.Add(col1);
dgresult.Columns.Add(col2);
}
}
class Result
{
int id;
int total;
int marks;
int avg;
public int Id { get { return id; } set { id = value; } }
public int Total { get { return total; } set { total = value; } }
public int Marks { get { return marks; } set { marks = value; } }
public int Avg { get { return avg; } set { avg = value; } }
public Result(int ID, int TOTAL, int MARKS, int AVG)
{
id = ID;
total = TOTAL;
marks = MARKS;
avg = AVG;
}
}
I don't understand why it is happening like this.
Thanks in advance.
I've added comments to your code that show my thoughts:
private void Form1_Load(object sender, EventArgs e)
{
// Make a new list of result class objects, local to the Form1_Load method:
List<Result> result = new List<Result>();
// Call a method that populates a list... wait, what list?
PopulateResult();
dgresult.AutoGenerateColumns = false;
dgresult.Items.Clear();
// Set the datagrid data source to the list created earlier
dgresult.ItemsSource = result;
// ...
I'm not sure why the datagrid has a duplicate set of columns after you've specified and added only two columns. It would help to see the method PopulateResult().
The list that PopulateResult() is adding to must be some other list, because the one created in Form1_Load is local in scope.
I'm not sure if this is just a little oversight, or if you need to learn about variable scope. Forgive me if this is already known to you:
C# Variable Scopes
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.
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.