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; }
}
Related
The ListBox's DataSource is bound to Detail.Tags.
When I select the first row, ListBox populates as expected.
When I select the second row, the expected (and desired) result is that the ListBox simply displays nothing, because ItemB's Detail property is purposely null for demonstration purposes, so ItemB's Detail.Tags doesn't exist.
Actual result is that program crashes to desktop with System.ArgumentException: 'Complex DataBinding accepts as a data source either an IList or an IListSource.'
Minimal reproducible example:
public partial class Form1 : Form
{
private readonly IList<Item> _items;
private BindingSource _bs = new BindingSource(){ DataSource = typeof(Item) };
public Form1()
{
InitializeComponent();
_items = GenerateSampleItems();
}
private void Form1_Load(object sender, EventArgs e)
{
dataGridView1.DataSource = _items;
listBox1.DataBindings.Add(new Binding("DataSource", _bs, "Detail.Tags", false, DataSourceUpdateMode.Never));
}
private void DataGridView1_SelectionChanged(object sender, EventArgs e)
{
if (dataGridView1.SelectedRows.Count == 1)
{
_bs.DataSource = dataGridView1.SelectedRows[0].DataBoundItem;
}
else
{
_bs.DataSource = typeof(Item);
}
}
private IList<Item> GenerateSampleItems()
{
return new List<Item>()
{
new Item()
{
Name = "ItemA"
,Detail = new Detail()
{
Expiration = new DateTime(2024,1,1)
,Tags = new BindingList<Tag>(new List<Tag>()
{
new Tag()
{
TagName = "FirstT"
,TagValue = "FirstV"
}
,new Tag()
{
TagName = "SecondT"
,TagValue = "SecondV"
}
})
}
}
,new Item()
{
Name = "ItemB"
// Detail purposely omitted
}
,new Item()
{
Name = "ItemC"
// Detail purposely omitted
}
};
}
}
class Item
{
public string Name { get; set; }
public Detail Detail { get; set; }
}
public class Detail
{
public DateTime Expiration { get; set; }
public BindingList<Tag> Tags { get; set; }
}
public class Tag
{
public string TagName { get; set; }
public string TagValue { get; set; }
}
You can solve this problem by Creating a BindingSource for each model:
Main BindingSource where its DataSource property is set to a list of Item. This one is the DataGridView.DataSource.
Second BindingSource to navigate the Detail data members of the main BindingSource.
Third one to navigate and display the Tags data members of the detail's BindingSource. This one is the ListBox.DataSource.
public partial class Form1 : Form
{
private IList<Item> _items;
private BindingSource _bsItems, _bsDetail, _bsTags;
public Form1()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_items = GenerateSampleItems();
_bsItems = new BindingSource(_items, null);
_bsDetail = new BindingSource(_bsItems, "Detail");
_bsTags = new BindingSource(_bsDetail, "Tags");
dataGridView1.DataSource = _bsItems;
listBox1.DataSource = _bsTags;
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
base.OnFormClosed(e);
_bsItems.Dispose();
_bsDetail.Dispose();
_bsTags.Dispose();
}
private IList<Item> GenerateSampleItems()
{
return new List<Item>()
{
new Item()
{
Name = "ItemA",
Detail = new Detail
{
Expiration = new DateTime(2024,1,1),
Tags = new BindingList<Tag>(new List<Tag>()
{
new Tag
{
TagName = "FirstT",
TagValue = "FirstV"
},
new Tag
{
TagName = "SecondT",
TagValue = "SecondV"
}
})
}
},
new Item()
{
Name = "ItemB"
// Detail purposely omitted
},
new Item()
{
Name = "ItemC"
// Detail purposely omitted
}
};
}
}
// Elsewhere within the project's namespace
public class Item
{
public string Name { get; set; }
public Detail Detail { get; set; }
// Optional: Change, remove as needed...
public override string ToString()
{
return $"Name: {Name} - Detail: {Detail}";
}
}
public class Detail
{
public DateTime Expiration { get; set; }
public BindingList<Tag> Tags { get; set; }
// Optional: Change, remove as needed...
public override string ToString()
{
var tags = $"[{string.Join(", ", Tags)}]";
return $"Expiration: {Expiration} - Tags: {tags}";
}
}
public class Tag
{
public string TagName { get; set; }
public string TagValue { get; set; }
// Optional: Change, remove as needed...
public override string ToString()
{
return $"{TagName}: {TagValue}";
}
}
That's all. No need to add DataBindings nor to handle the grid's SelectionChanged event as shown in your code snippet.
On the other hand, if you need to display the selected Item.Detail.Tags, then you need to flatten them in a list whenever the grid's selection changes and bind the result to the ListBox.
// +
using System.Linq;
public partial class Form1 : Form
{
private BindingSource _bsItems;
public Form1() => InitializeComponent();
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_bsItems = new BindingSource(GenerateSampleItems(), null);
dataGridView1.DataSource = _bsItems;
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
base.OnFormClosed(e);
_bsItems.Dispose();
}
private void dataGridView1_SelectionChanged(object sender, EventArgs e)
{
listBox1.DataSource = dataGridView1.SelectedCells
.Cast<DataGridViewCell>()
.Select(cell => cell.OwningRow).Distinct()
.Where(row => (row.DataBoundItem as Item)?.Detail != null)
.SelectMany(row => (row.DataBoundItem as Item).Detail.Tags)
.ToList();
}
}
I have two buttons and bind their property to two properties of a data object.
But every property is updated when I call PropertyChanged of the data object.
public partial class Form1 : Form
{
private DataClass data = new DataClass();
public Form1()
{
InitializeComponent();
ButtonA.DataBindings.Add("Text", data, "DataA");
ButtonB.DataBindings.Add("Text", data, "DataB");
ButtonB.Click += new EventHandler(OnButtonBClicked);
}
private void OnButtonBClicked(object sender, EventArgs e)
{
data.DataA += "1";
data.DataB += "1";
data.Notify("DataB");
}
}
public class DataClass : INotifyPropertyChanged
{
public string DataA { get; set; }
public string DataB { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public DataClass() {}
public void Notify(string property_name)
{
PropertyChanged(this, new PropertyChangedEventArgs(property_name));
}
}
When I press ButtonB (which means I call PropertyChanged(this, new PropertyChangedEventArgs("DataB"))), both ButtonA and ButtonB show new text.
If I call PropertyChanged(this, new PropertyChangedEventArgs("DataA")), both buttons are updated.
If I don't change value of DataA / DataB and just call PropertyChanged(this, new PropertyChangedEventArgs("DataB")), still both buttons are updated (can be noticed by breakpoint debugging).
If I call PropertyChanged(this, new PropertyChangedEventArgs("QQQ")), then no button is updated.
PropertyChangedEventArgs has a property named propertyName, I thought it's used to specify one property to notify but it doesn't.
In my real code, DataB changes much more frequently than DataA. I don't want to update ButtonA each time DataB is changed, it takes too much time.
Question: why would this happen? When a data source property is changed, how can I only update properties really connected to it?
(All code is .Net Framework 4.7.1 on Windows.)
#Jimi's method works.Simple and effective.I put each property in a shell class and bind data to the shell:
public class MyProperty<T>: INotifyPropertyChanged
{
public T Content { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public MyProperty(T _content)
{
Content = _content;
}
public void Notify()
{
PropertyChanged(this, new PropertyChangedEventArgs("Content"));
}
}
public class DataClass
{
public MyProperty<string> DataA = new MyProperty<string>("");
public MyProperty<string> DataB = new MyProperty<string>("");
public DataClass() {}
}
But in this way I must use DataA.Content+="1" instead of DataA+="1" every where.
I decide to use a base class to create all shells.But my real DataClass must inherit from other class and C# don't support multi-inherit.So I have to use a extension class.
public class BindHandle<T> : INotifyPropertyChanged
{
public T Content { get { return (T)parent.GetType().GetProperty(prop_name).GetValue(parent); } }
private object parent;
private string prop_name;
public event PropertyChangedEventHandler PropertyChanged;
public BindHandle(object _parent, string _prop_name)
{
parent = _parent;
prop_name = _prop_name;
}
public void NotifyChange()
{
PropertyChanged(this, new PropertyChangedEventArgs("Content"));
}
}
public interface IBindHandleProvider
{
BindHandleProvider provider { get; set; }
}
public class BindHandleProvider
{
private Dictionary<string, object> handle_map = new Dictionary<string, object>();
public BindHandle<T> GetBindHandle<T>(object obj,string property_name)
{
if (!handle_map.ContainsKey(property_name))
handle_map.Add(property_name, new BindHandle<T>(obj, property_name));
return (BindHandle<T>)handle_map[property_name];
}
public void NotifyChange<T>(string property_name)
{
if (handle_map.ContainsKey(property_name))
((BindHandle<T>)handle_map[property_name]).NotifyChange();
}
}
public static class BindHandleProviderExtension
{
public static void NotifyChange<T>(this IBindHandleProvider obj, string property_name)
{
obj.provider.NotifyChange<T>(property_name);
}
public static BindHandle<T> GetBindHandle<T>(this IBindHandleProvider obj, string property_name)
{
return obj.provider.GetBindHandle<T>(obj,property_name);
}
}
public class DataClass:IBindHandleProvider
{
public BindHandleProvider provider { get; set; } = new BindHandleProvider();
public string DataA { get; set; } = "";
public string DataB { get; set; } = "";
public DataClass(){ }
}
Then bind it like
ButtonA.DataBindings.Add("Text", data.GetBindHandle<string>("DataA"), "Content");
And notify like
data.NotifyChange<string>("DataB");
It's kinda complex but works well.
I have an observable collection that is shared between different viewmodels.
public class UserInput1ViewModel: INotifyPropertyChanged
{
public ObservableCollection<ParamClass> ParamColl { get; set; }
public UserInput1ViewModel(<ParamClass> paramColl)
{
this.ParamColl = paramColl;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
private void UpdateCollection()
{
this.ParamList = PerformCalculations();
}
}
public class ParamClass
{
public double Property1 { get; set; }
public double Property2 { get; set; }
public double Property3 { get; set; }
... ...
... ...
public double Property19 { get; set; }
}
The function PerformCalculations() will execute, but it will not update the all the properties inside the observable collection. I have learned that you cannot do that with observable collection https://stackoverflow.com/a/9984424/4387406.
So, this is what I am currently doing.
private void UpdateCollection()
{
var output = PerformCalculations();
for(int i = 0; i < output.Count(); i++)
{
this.ParamColl[i].Property1 = output[i].Property1;
this.ParamColl[i].Property2 = output[i].Property2;
... ...
... ...
this.ParamColl[i].Property19 = output[i].Property19;
}
}
My question is: is there a better way of sharing observable collection?
Many thanks in advance.
If you want the GUI to update whenever a property of an instance in a list changes, you should implement the INotifyPropertyChanged in the instance class, just as you have done in your ViewModel.
You haven't shown what your ParamClass looks like so I'm using a Person class in place. You could do the same thing as you've done in your ViewModel.
public class Person : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set { name = value; OnPropertyChanged("Name"); }
}
private int age;
public int Age
{
get { return age; }
set { age = value; OnPropertyChanged("Age"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
Now, if even a single property in any of the instances is changed it will be reflected on your GUI.
Since you're using WPF, there's quite a few good MVVM tool-kits out there that will do a lot of this for you. For instance, MVVM Light Toolkit is one such examples. There's many others out there.
I'm having trouble copying data from one ObservableCollection to another. I have an api call GetItemsAsync from http that puts the response into a model called ShipList.cs. Inside of ShipList.cs there is ShipCatalog[] ships. I have created a second model called HangarList.cs with HangarCatalog[] hangars. I have a page that displays the master list of ships (ShipsList) I want the user to select the ship (ShipList.name is bound to this particular ListVIew. I tried to use .Where() to filter ShipList to only the match to the selected item and copy that data to HangarCatalog. I'm getting Cannot convert GallogForms.Api.ShipCatalog to GallogForms.Api.HangarCatalog using the following code.
ViewModel
private ShipCatalog _selectedShip;
public ShipCatalog SelectedShip
{
get {return _selectedShip; }
set
{if (_selectedShip != value)
_selectedShip = value;
id = _selectedShip.id;
CopyShipData();
private async void CopyShipData()
{
var _container = Items.Where(s =>
s.name.FirstOrDefault().ToString() == id.ToString()).ToList();
foreach (var item in _container.Where(s =>
s.name.FirstOrDefault().ToString() == id.ToString()).ToList())
// var items = await _gallogClient.GetItemsAsync<ShipList>();
// foreach (var item in items.ships.Where(s =>
// s.name.FirstOrDefault().ToString() == id.ToString()).ToList())
{
Hangars.Clear();
Hangars.Add(item);
}
}
I haven't found any answer yet, and I've read plenty, that can address my situation. myShipsList is bound to a new model I've created in the API that perfectly mirrors ShipCatalog[].
I've also keep running across answers that suggest ListViewItem.Item or in my case SuggestedShipView.Items. .Items is not an option for my ListViews in the view model.
AddShipPage.xaml
<StackLayout Orientation="Vertical">
<SearchBar x:Name="HangarListView" Text="Add To Your Fleet!"
TextChanged="HangarList_TextChanged"
BackgroundColor="Azure"
/>
<Grid>
<ListView x:Name="SuggestedShipView"
ItemsSource="{Binding Items}"
SelectedItem="{Binding selectedShip}"
BackgroundColor="Silver">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
.....................
ShipList.cs (API Query)
[ApiPath("ships")]
public class ShipList : ApiQueryable
{
public ShipCatalog[] ships { get; set; }
}
public class ShipCatalog : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public int id { get; set; }
public string name { get; set; }
public string uri { get; set; }
public int rsi_id { get; set; }
public string img { get; set; }
public string mfr { get; set; }
public string flyable { get; set; }
public string scu { get; set; }
public string value { get; set; }
public string bgcolor { get; set; }
public string color { get; set; }
public string role { get; set; }
public bool _isVisible { get; set; }
public bool IsVisible
{
get { return _isVisible; }
set
{
if (_isVisible != value)
{
_isVisible = value;
OnPropertyChanged();
}
}
}
}
}
HangarList mirrors ShipList perfectly with the exception it's named HangarList, public HangarCatalog[] hangars
And Finally, the query which populates ShipCatalog[]
AddShipViewModel
Items.Clear();
var items = await _gallogClient.GetItemsAsync<ShipList>();
foreach (var item in items.ships.ToList())
{
Items.Add(item);
}
No error messages per se, but I have not been able to structure a method to complete this task. If you would like to see the entire project to see more of what I have going on, http://github.com/dreamsforgotten/GallogMobile
when you tap or select an item in a ListView, the second parameter of the event handler will contain a reference to the item selected/tapped. You just need to cast it to the correct type. Then you can reference all of its properties as needed
private void SuggestedShipView_ItemTapped(object sender, ItemTappedEventArgs e)
{
// e.Item is the specific item selected
ShipCatalog ship = (ShipCatalog)e.Item;
// you can then use this ship object as the data source for your Hangar list/control,
// and/or add it to another List that is just the items the user has selected
}
I have a ListView with an List<List<enum>> property and i have a View that shows each bool as button.
I want to cycle through the bound enum when someone clicks on the button. The problem is I cant use a normal click because it would be outside of my ViewModel.
Edit:
I have the class TruthTable that has 2 DynTable:
public sealed class Column<T>
{
public Column()
{
ColumnData = new List<T>();
ColumnHeader = "";
}
...
public List<T> ColumnData { get; set; }
public string ColumnHeader { get; set; }
}
public sealed class DynTable<T>
{
public DynTable()
{
Columns = new List<Column<T>>();
}
...
public List<Column<T>> Columns { get; set; }
}
public sealed class TruthTable
{
public TruthTable()
{
input = new DynTable<bool>();
results = new DynTable<BoolState>();
}
...
private DynTable<bool> input;
private DynTable<BoolState> results;
public DynTable<bool> Input { get { return input; } set { input = value; } }
public DynTable<BoolState> Results { get { return results; }}
}
public enum BoolState
{
False = 0,
True = 1,
DontCare = 2
}
And i have a ViewModel for the TruthTable. I dont think that the I have to show the code for the ViewModel because its just a TruthTable property. I hope thats enough code to understand my problem ._.