LiveCharts (LVC) WPF - get chart from DataClick event - c#

I've got a 'dashboard' with several charts on it. One of them is a pie chart with a number of series.
LiveCharts has a DataClick event
DataClick(object sender, ChartPoint chartPoint)
sender is of type PieSlice. How can i access SeriesCollection from that event or, alternatively, chart name / id?
What I am trying to achieve is access chart that sent the event, then it's series collection and check which of the series / pie slice fired the event.

First and foremost, don't use events, use commands - that's the MVVM way. i.e.
<LiveCharts:PieChart DataClickCommand="{Binding DrillDownCommand}" Series="{Binding MySeries}" ...>
Note the binding to MySeries:
public SeriesCollection MySeries
{
get
{
var seriesCollection = new SeriesCollection(mapper);
seriesCollection.Add(new PieSeries()
{
Title = "My display name",
Values = new ChartValues<YourObjectHere>(new[] { anInstanceOfYourObjectHere })
});
return seriesCollection;
}
}
And about handling the command:
public ICommand DrillDownCommand
{
get
{
return new RelayCommand<ChartPoint>(this.OnDrillDownCommand);
}
}
private void OnDrillDownCommand(ChartPoint chartPoint)
{
// access the chartPoint.Instance (Cast it to YourObjectHere and access its properties)
}

You need to work with arguments, not the sender. The second parameter is ChartPoint, that cointains SeriesView. So just access it and use it's Title:
private void Chart_OnDataClick(object sender, ChartPoint chartpoint) {
MessageBox.Show(chartpoint.SeriesView.Title);
}
How can i access SeriesCollection from that event or, alternatively, chart name / id?
SeriesView is not the whole SeriesCollection, but Series you clicked on. And you can have it's name

Related

Responding to object property changes within a class object hierarchy

I am developing an application which uses a hierarchical object structure and displays a few key object properties from those objects on the main GUI within a DataGridView. Those values must update when the underlying data changes. I have considered a few options:
Bind the individual DataGridView cells to the relevant object properties. I understand that this is not possible, and DGV binding is all or nothing.
Dynamically position Textboxes over the grid cells and bind those, but this seems messy.
Create an intermediate list/array/collection which references only the relevant object properties, and then use that list as a data source for the DataGridView.
Respond to the PropertyChanged events. The complication is that I have got multiple classes. The top-level object exists within the UI scope, and has a child object which in turn may have multiple child objects of its own, and so on. The UI can access properties of all objects, but not their events.
I have been looking at passing the PropertyChanged event from whichever level it occurs up the chain so that it can be handled within the UI. So within a particular class I want to respond to OnPropertyChanged within that class, and within any children, and make any events raised available to the parent class. Thus events would flow up the tree to the top.
I understand how to do the two steps individually, I think, with reference to the following:
Handling OnPropertyChanged
Pass click event of child control to the parent control
However, although I presume the two can be combined, I am not quite sure how to do this. In the UI I have got this:
project.PropertyChanged += new PropertyChangedEventHandler(ProjectPropertyChanged);
private void ProjectPropertyChanged(object sender, PropertyChangedEventArgs e) {
MessageBox.Show("In main UI. Project property changed!");
}
And then one level down I have got something like this:
public class Project : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Project() {
childObject.PropertyChanged += new PropertyChangedEventHandler(ProjectPropertyChanged);
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
ProjectPropertyChanged(sender, e); // this doesn't work due to different parameters
}
private void PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// Event available to parent class
}
}
The idea being that each class would pass its own OnPropertyChanged() events to its PropertyChanged() method, and respond to its children's OnPropertyChanged() events, and expose all to the parent class.
If doing this, ideally I would like to retain knowledge of which property changed in order to respond accordingly.
The most immediate issue is lack of compatibility between ProjectPropertyChanged and OnPropertyChanged due to different parameters. More fundamentally, though, I am not sure whether this method is workable or optimal.
How best to do this?
To answer my own question:
I tried unsuccessfully to do this with an intermediate binding source, using a DataTable (as per this question.). The problem was that I couldn't create references to the data objects. The DataTable seemed to contain values.
So I ended up using a method I was more sure about, but is less elegant, which is option 2 in my question above. I position Labels where I need bound data values, and bind to those labels. This works.
With some simplification, and pretending that our objects are animals, my solution was this:
Label[,] dashboardLabels = new Label[3,14];
private void Form1_Shown(object sender, EventArgs e)
{
CreateLabels(); // create and position labels (no binding yet)
}
public void CreateLabels(int cols = 3, int rows = 14)
{
for (int col = 0; col < cols; col++)
{
for (int row = 0; row < rows; row++)
{
// Create label...
Label l = new Label();
l.Text = "N/A";
l.ForeColor = Color.Red
dashboardLabels[col, row] = l;
this.Controls.Add(l);
// Position label over DGV cell...
Point dgvCell = dataGridView1.GetCellDisplayRectangle(col + 2, rowNumbersLabel[row], false).Location;
Point dgvGrid = dataGridView1.Location;
l.Left = dgvGrid.X + dgvCell.X;
l.Top = dgvGrid.Y + dgvCell.Y;
}
}
}
private void UpdateLabels(List<Dog> dogs)
{
for (int i = 0; i < dogs; i++)
{
if (!dashboardLabels[i, 0].Visible) dashboardLabels[i, 0].Visible = true;
if (dogs[i].IsSetUp) BindLabel(dashboardLabels[i, 0], dogs[i],"Name");
}
}
private void BindLabel(Label l, Dog dog, string observation)
{
Binding b = new Binding("Text", dog, observation);
l.DataBindings.Add(b);
l.ForeColor = Color.Green;
}
}
Then when the objects are created, I call UpdateLabels(). If not initialised, the label will show 'N/A' in red at this point. If initialised, the label will become green and will be bound to the object's name so it will update automatically from that point on.
I did much searching and the information I was finding suggested that a DataGridView does not support complex data binding i.e. it is pretty much one class to one DGV, or not at all. I couldn't find an alternative grid-like control which would do it either.

Implement INotifyPropertyChanged on a list of objects?

Hmm, not sure if I chose the right approach.
I have a grid of components. In the first column there are DatePickers.
In the second column there are combo-boxes. In the last column there are text-boxes. The grid has 15 rows.
I named them by their column and row number as you would number cells in a grid.
So dp1_1 for DatePicker are position (1,1), dp2_1 for position (2,1).
cb1_1 for ComboBox are position (1,1), cb2_1 for ComboBox position (2,1).
I keep my date pickers data, combo-boxes data, text-boxes data in an ordinary list for easy access/reference, like so:
public int numOfRows = 15;
private List<DateTime> _MyDateTimeList = new List<DateTime>();
public List<DateTime> MyDateTimeList
{
get { return _MyDateTimeList; }
set {
DateTime pomDatumObjava;
_MyDateTimeList = value;
for (int i = 0; i < numOfRows; i += 1)
{
pomDatumObjava = new DateTime();
// code for accessing/enabling/disabling the appropriate date picker, which doesn't work since I don't know how to send the window reference where my date pickers reside
// pomDatumObjava = Utils.enableDisableDatePicker(null, Constants.DP_LABEL + stringIndex, true, 1).SelectedDate.Value;
_MyDateTimeList.Add(pomDatumObjava);
}
OnPropertyChanged("MyDateTimeList");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
Console.WriteLine("OnPropertyChanged -> " + name);
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
Console.WriteLine("handler != null -> " + name);
handler(this, new PropertyChangedEventArgs(name));
}
}
public static DatePicker enableDisableDatePicker(System.Windows.Window myWindow, string name, bool enableDisable, double op)
{
DatePicker dp = (DatePicker)myWindow.FindName(name);
if (!enableDisable)
{
dp.SelectedDate = null;
}
dp.IsEnabled = enableDisable;
dp.Opacity = op;
return dp;
}
How do I access my components in the window and reference them appropriately so that each time I change a value in certain DatePicker, I detect the change in the list?
You can find the Utils function in comments line. Where it says null, there should be the window objects where my components are placed.
Or, is this the right approach?
I will have lot of components(15x3 = 45 x code for OnPropertyChanged), so the MVVM file will be quite large to set OnPropertyChanged() for all of them.
As ASh says, you need an ObservableCollection of objects, one for each row. ObservableCollections automatically update their bound controls when you add or remove objects, and pass on events when the objects change. These objects would presumably have three properties (for the datepicker, combobox and text box) that have OnPropertyChanged().
Then bind the ObservableCollection to the ItemSource of your grid, and the three controls to the three properties of an item.
For MVVM, you shouldn't ever need to reference a control in the View. Instead the view should reflect the state of the ViewModel. If you want to disable a datepicker, it's Enabled property should be bound to some thing that raises OnPropertyChanged().
If you post your view, we can suggest how to do this.

Binding data MVVM after Tap on ListBox

This is my situation:
- I have a WebApi that sends me a json data.
- My app reads this data and binds all information in a list box
- When I tap on Item of the list box I want to show all information about that item
The problem is: How can I bind data on the new view?
This is the code after tap (MainViewModel):
this.ProfessorDetail = new RelayCommand(() =>
{
if (SelectedIndexProfessors != -1)
{
//This variable contain all detail information
Professor x = Professors.ElementAt(SelectedIndexProfessors);
//Open new page
App.RootFrame.Navigate(new Uri("/Pages/ProfessorDetailPage.xaml", UriKind.RelativeOrAbsolute));
}
});
You can use Uri parameter to pass simple string information between pages. For example, in your RelayCommand pass unique information about selected professor :
.........
//pass selected professor Id to ProfessorDetailPage
App.RootFrame.Navigate(new Uri("/Pages/ProfessorDetailPage.xaml?professorId=" + x.ProfessorId, UriKind.RelativeOrAbsolute));
.........
Then in the ProfessorDetailPage's Loaded or NavigatedTo event handler get the uri parameter and display information accordingly :
.........
string professorId;
if(NavigationContext.QueryString.TryGetValue("professorId", out professorId))
{
//load information based on professorId parameter value
}
.........
I never made a page based application, but I was looking at this link and from basic knowledge of WPF, I do know you need to set the DataContext of that page just like you asked.
Take a look at the following code in the link:
this.Frame.Navigate(typeof(BasicPage2), tb1.Text);
That is sending the text entered in tb1 to the object you're navigating to. Then look at how the receiving object is utilizing that information:
private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
string name = e.NavigationParameter as string;
....
}
You're going to want to follow the same idea, except you're going to want to do something like:
App.RootFrame.Navigate(new Uri(".....", ...), x);
And then in the page itself, you're going to want to set up a LoadState event in ProfessorDetailPage control and inside it do:
Professor prof = x as Professor;
if( prof != null)
{
this.DataContext = prof;
}
That should set the DataContext of ProfessorDetailPage and your data should be populated.
Let me know how it works out, hope this helps!

Insert Text and Image in HubTile after online-content is loaded

I'm new to WP8 and follow many tutorials. For parts of the menu I use a viewModel with NotifyPropertyChanged. When I get my list of news articles it creates a viewModel and displays it in a longListSelector.
But also I want to make 1 HubTile with the image and some preview-text of the first article. Is there a nice way to send some event to the .xaml.cs? Or do I have to make another viewModel for this one HubTile and make a binding?
Ony try was to make such a variable:
private bool _isDataLoaded = false;
public bool IsDataLoaded
{
get
{
return _isDataLoaded;
}
set
{
if (value != _isDataLoaded)
{
_isDataLoaded = value;
NotifyPropertyChanged("IsDataLoaded");
}
}
}
The same thing is used with "IsLoading"-variable to create a loading-indicator in the systemTray:
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("MainPage_Loaded-Funktion");
Binding binding = new Binding("IsLoading") { Source = DataContext };
BindingOperations.SetBinding(
prog, ProgressIndicator.IsVisibleProperty, binding);
binding = new Binding("IsLoading") { Source = DataContext };
BindingOperations.SetBinding(
prog, ProgressIndicator.IsIndeterminateProperty, binding);
prog.Text = "Lade aktuelle Inhalte...";
}
Can I use this to call a function, when my variable is set and I get a notification?
The solution that helped me out was this:
<toolkit:HubTile Message="{Binding OnlineNews[0].TeaserText}"/>
Didn't know that you can access the viewModel like that. Thanks to Toni Petrina!

Show tooltip in LineSeries WinForms Chart?

I am working on a Dashboard System where i am using Line Chart in WinForms. I need to show the tooptip on each line. I have tried this
var series = new Series
{
Name = chartPoint.SetName,
Color = chartPoint.ChartColor,
ChartType = SeriesChartType.Line,
BorderDashStyle = chartPoint.ChartDashStyle,
BorderWidth = chartPoint.BorderWidth,
IsVisibleInLegend = !chartPoint.HideLegend,
ToolTip = "Hello World"
};
but its not working for me
You have two options either use Keywords accepted by the Chart control.
myChart.Series[0].ToolTip = "Name #SERIESNAME : X - #VALX{F2} , Y - #VALY{F2}";
In the Chart control, a Keyword is a character sequence that is replaced with an automatically calculated value at run time. For a comprehensive list of keywords accepted by the Chart control look up Keyword reference
or
if you want something more fanciful, you have to handle the event GetToolTipText
this.myChart.GetToolTipText += new System.Windows.Forms.DataVisualization.Charting.Chart.ToolTipEventHandler(this.myChart_GetToolTipText);
Now I am not sure what you want to show on the ToolTip but you could add the logic accordingly. Assuming you want to show the values of the DataPoints in the series
private void myChart_GetToolTipText(object sender, System.Windows.Forms.DataVisualization.Charting.ToolTipEventArgs e)
{
switch(e.HitTestResult.ChartElementType)
{
case ChartElementType.DataPoint:
e.Text = myChart.Series[0].Points[e.HitTestResult.PointIndex]).ToString()
+ /* something for which no keyword exists */;
break;
case ChartElementType.Axis:
// add logic here
case ....
.
.
default:
// do nothing
}
After some RnD i got tooltips on Line Series, but still confused why its not working with this solution.
Here is the solution
series.ToolTip = string.Format("Name '{0}' : X - {1} , Y - {2}", chartPoint.SetName, "#VALX{F2}",
"#VALY{F2}");
mainChartControl.GetToolTipText += ChartControlGetToolTipText;
private void ChartControlGetToolTipText(object sender, ToolTipEventArgs e)
{
}

Categories