Adding series to OxyPlot chart programmatically: nothing displayed - c#

I'm trying to use OxyPlot in a scenario where I'm setting up the chart type, and then adding 1 or more series to it programmatically. To this end:
in the XAML view, I simply add a PlotView control.
in the code, my class has a PlotModel property (notifying changes), and an AddSeries method. Whenever this is invoked, I setup the plot model if not yet configured (I create a new PlotModel object and add a LinearAxis), create a random data LineSeries, and add it to the Series collection of the plot model. Finally, I invoke InvalidatePlot(true) on it to force it to redraw.
Yet, nothing is displayed when I execute this code, which raises no error.
I googled around a bit but the only code samples I found refer to the typical scenario where a XAML view binds to a bound datasource; but here I'm recreating the PlotModel each time, according to the chart type and series count. You can find a dummy repro solution here: http://1drv.ms/1R8EFBc . Just compile and run, and click Add series. Could anyone suggest a solution?

The bound property PlotModel is never updated due to:
private void SetupPieChart()
{
if (_plot == null) _plot = new PlotModel();
else _plot.Axes.Clear();
}
Change to following and it should work:
private void SetupPieChart()
{
if (PlotModel == null) PlotModel = new PlotModel();
else PlotModel.Axes.Clear();
}
Since :
private PlotModel _plot;
public PlotModel PlotModel
{
get { return _plot; }
set
{
_plot = value;
OnPropertyChanged(); // <=== update here
}
}

Related

How to avoid flickering when using an asynchronous binding

I've created an example to illustrate my problem.
ViewModel:
public class VM : INotifyPropertyChanged
{
private double _value = 1;
public double Value
{
get { return _value; }
set
{
_value = value;
OnPropertyChanged();
}
}
public VM()
{
var timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromTicks(1);
timer.Tick += (s, e) => { Value += 1; };
timer.Start();
}
// OnPropertyChanged stuff ...
}
}
View:
<Window.DataContext>
<namespace:VM/>
</Window.DataContext>
<Grid>
<TextBox Text="{Binding Value, IsAsync=True, FallbackValue=Test}"/>
</Grid>
When running my application the text in the textbox flickers. During the update process the FallbackValue is displayed, which makes no sense to me.
Does anyone knows the purposes or what are the benefits that during the update process the FallbackValue is displayed? Is there a way to display the old Value during an async update process?
This seems normal to me, given that you are using IsAsync=True in your binding. From the documentation:
While waiting for the value to arrive, the binding reports the FallbackValue, if one is available
When the PropertyChanged event is raised, WPF initiates the process of updating the target of the binding. Normally this would happen synchronously, with the property getter called immediately to update the value.
But you are using IsAysnc=True, so instead WPF fills in the target with the fallback value, and starts an asynchronous request to retrieve the actual property value later. Until that request has completed, the fallback value is displayed.
Does anyone knows the purposes or what are the benefits that during the update process the FallbackValue is displayed?
Per the documentation, the intent behind the IsAsync=True setting is that it's used when the property getter is, or could be, slow. Your code has told WPF that the property value has changed, so it knows the old value is no longer valid. Your code has also told (via the IsAsync in the XAML) that the property getter could take some time to provide the new value, so it defers retrieving that value until later.
In the meantime, what should WPF display? That's what the fallback value is there for.
Is there a way to display the old Value during an async update process?
If you don't want the behavior that is designed for this feature in WPF, you should just retrieve the new data asynchronously yourself, and update the property via the setter when you have it. It's not a good idea for a property getter to be slow anyway, so this would be a better design in any case.
I had the same problem but with an image source. I've removed the IsAsync on the binding and I have made my getter async:
// This field hold a copy of the thumbnail.
BitmapImage thumbnail;
// Boolean to avoid loading the image multiple times.
bool loadThumbnailInProgress;
// I'm using object as the type of the property in order to be able to return
// UnsetValue.
public object Thumbnail
{
get {
if (thumbnail != null) return thumbnail;
if (!loadThumbnailInProgress) {
// Using BeginInvoke() allow to load the image asynchronously.
dispatcher.BeginInvoke(new Action(() => {
thumbnail = LoadThumbnail();
RaisePropertyChanged(nameof(Thumbnail));
}));
loadThumbnailInProgress = true;
}
// Returning UnsetValue tells WPF to use the fallback value.
return DependencyProperty.UnsetValue;
}
}
Sometimes a binding will fail, failure is important to consider. Fallback value option presents users a message if an error occurs, rather than nothing happening. If you would like your fallbackvalue to display the previous value contained, I could think of a few ways of trying : possibly saving the value in a reference string and/or to another control, then binding to that control
But if you don't want the fallbackvalue displayed at all, you need to do a code inspection to see how your binding is failing/or is slow, and contain it in your code behind
I've found an approach to avoid flickering by just inheriting from textbox and overriding it's textproperty-metadata.
Custom TextBoxControl
public class CustomTextBox : TextBox
{
static CustomTextBox()
{
TextProperty.OverrideMetadata(typeof(CustomTextBox), new FrameworkPropertyMetadata(null, null, CoerceChanged));
}
private static object CoerceChanged(DependencyObject d, object basevalue)
{
var tb = d as TextBox;
if (basevalue == null)
{
return tb.Text;
}
return basevalue;
}
}
View
<Window.DataContext>
<namespace:VM/>
</Window.DataContext>
<Grid>
<namespace:CustomTextBox Text="{Binding Value, IsAsync=True}"/>
</Grid>
It's important to have the text-binding without a fallbackvalue. So during update process the text is set to the textproperty defalut value - so in this case to null.
The CoerceChanged handler checks whether the new value is null. If it's so he returns the old value so that during update process there is still the old value displayed.

OxyPlot, Points not in LineSeries

I have a hard time getting OxyPlot to display my data in my standalone WPF application (I use caliburn micro, and follow the MVVM pattern).
So in my Xaml I have the following:
<UserControl ...
xmlns:oxy="clr-namespace:OxyPlot.Wpf;assembly=OxyPlot.Wpf">
and later
<oxy:Plot Title="Winnings of Selected Player" Model="{Binding Graph1}"/>
In my ViewModel, I seem to have the choice to either include using OxyPlot; or using OxyPlot.Wpf;. If I do the former, I can define my PlotModel as follows:
private PlotModel _graph1;
public PlotModel Graph1 {
get { return _graph1;}
set {
_graph1 = value;
Graph1.RefreshPlot(true);
}
}
public MyViewModel() {
Graph1 = new PlotModel();
var FirstSeries = new LineSeries();
FirstSeries.Points.Add(new DataPoint(1,2));
FirstSeries.Points.Add(new DataPoint(1,2));
}
but my view does not display anything (in fact, I don't even see an 'empty graph', or the title in the view). I have tried scattering the RefreshPlot command all over the place, too ...
So then, I figure to try using OxyPlot.Wpf; instead. Then, however, I cannot add Points to my LineSeries - here's a screenshot of my IntelliSense ...
!(http://imageshack.com/a/img30/2441/cvqy.png)
So how do I populate data into my LineSeries using OxyPlot.Wpf? By the way, I still don't see an empty graph, even when I compile without adding points to the LineSeries. All examples I have looked at do this, or they write code in tha .xaml.cs code-behind file which I don't want to do.
EDIT: As dellywheel mentiones, I forgot to add the LineSeries to my PlotModel above when asking the question, so the constructor above should contain the line
Graph1.Series.Add(FirstSeries);
But that was just in typing the question, ofc not the real problem ...
You havent added the LineSeries to the Graph and the Graph is Graph1 not Graph
Try
public MyViewModel(){
Graph1 = new PlotModel();
var firstSeries = new LineSeries();
firstSeries.Points.Add(new DataPoint(1, 2));
firstSeries.Points.Add(new DataPoint(1, 2));
Graph1.Series.Add(firstSeries);
}
Hope that helps

WPF ListView: Changing ItemsSource does not change ListView

I am using a ListView control to display some lines of data. There is a background task which receives external updates to the content of the list. The newly received data may contain less, more or the same number of items and also the items itself may have changed.
The ListView.ItemsSource is bound to an OberservableCollection (_itemList) so that changes to _itemList should be visible also in the ListView.
_itemList = new ObservableCollection<PmemCombItem>();
_itemList.CollectionChanged += new NotifyCollectionChangedEventHandler(OnCollectionChanged);
L_PmemCombList.ItemsSource = _itemList;
In order to avoid refreshing the complete ListView I do a simple comparison of the newly retrieved list with the current _itemList, change items which are not the same and add/remove items if necessary. The collection "newList" contains newly created objects, so replacing an item in _itemList is correctly sending a "Refresh" notification (which I can log by using the event handler OnCollectionChanged of the ObservableCollection`)
Action action = () =>
{
for (int i = 0; i < newList.Count; i++)
{
// item exists in old list -> replace if changed
if (i < _itemList.Count)
{
if (!_itemList[i].SameDataAs(newList[i]))
_itemList[i] = newList[i];
}
// new list contains more items -> add items
else
_itemList.Add(newList[i]);
}
// new list contains less items -> remove items
for (int i = _itemList.Count - 1; i >= newList.Count; i--)
_itemList.RemoveAt(i);
};
Dispatcher.BeginInvoke(DispatcherPriority.Background, action);
My problem is that if many items are changed in this loop, the ListView is NOT refreshing and the data on screen stay as they are...and this I don't understand.
Even a simpler version like this (exchanging ALL elements)
List<PmemCombItem> newList = new List<PmemCombItem>();
foreach (PmemViewItem comb in combList)
newList.Add(new PmemCombItem(comb));
if (_itemList.Count == newList.Count)
for (int i = 0; i < newList.Count; i++)
_itemList[i] = newList[i];
else
{
_itemList.Clear();
foreach (PmemCombItem item in newList)
_itemList.Add(item);
}
is not working properly
Any clue on this?
UPDATE
If I call the following code manually after updating all elements, everything works fine
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
But of course this causes the UI to update everything which I still want to avoid.
After a change, you can use the following to refresh the Listview, it's more easy
listView.Items.Refresh();
This is what I had to do to get it to work.
MyListView.ItemsSource = null;
MyListView.ItemsSource = MyDataSource;
I know that's an old question, but I just stumbled upon this issue. I didn't really want to use the null assignation trick or the refresh for just a field that was updated.
So, after looking at MSDN, I found this article:
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?redirectedfrom=MSDN&view=netframework-4.7.2
To summarize, you just need the item to implement this interface and it will automatically detect that this object can be observed.
public class MyItem : INotifyPropertyChanged
{
private string status;
public string Status
{
get => status;
set
{
OnPropertyChanged(nameof(Status));
status = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
So, the event will be called everytime someone changes the Status. And, in your case, the listview will add a handler automatically on the PropertyChanged event.
This doesn't really handle the issue in your case (add/remove).
But for that, I would suggest that you have a look at BindingList<T>
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.bindinglist-1?view=netframework-4.7.2
Using the same pattern, your listview will be updated properly without using any tricks.
You should not reset ItemsSource of ListView each time observable collection changed. Just set proper binding that will do your trick. In xaml:
<ListView ItemsSource='{Binding ItemsCollection}'
...
</ListView>
And in code-behind (suggest to use MVVM) property that will be responsible for holding _itemList:
public ObservableCollection<PmemCombItem> ItemsCollection
{
get
{
if (_itemList == null)
{
_itemList = new ObservableCollection<PmemCombItem>();
}
return _itemList;
}
}
UPDATE:
There is similar post which most probably will Answer your question: How do I update an ObservableCollection via a worker thread?
I found a way to do it. It is not really that great but it works.
YourList.ItemsSource = null;
// Update the List containing your elements (lets call it x)
YourList.ItemsSource = x;
this should refresh your ListView (it works for my UAP :) )
An alternative on Xopher's answer.
MyListView.ItemsSource = MyDataSource.ToList();
This refreshes the Listview because it's a other list.
Please check this answer:
Passing ListView Items to Commands using Prism Library
List view Items needs to notify about changes (done is setter)
public ObservableCollection<Model.Step> Steps
{
get { return _steps; }
set { SetProperty(ref _steps, value); }
}
and UpdateSourceTrigger need to be set in xaml
<Image Source="{Binding ImageData, UpdateSourceTrigger=PropertyChanged}" />

Set Combobox Items and DataSource

I'm having a problem where every time I build my solution, the compile succeeds but when I run my program it will error as the forms designer.cs file has had the data source for my custom comboboxes added to it automatically; resulting in an exception stating
Items collection cannot be modified when the DataSource property is set.
Any ideas on what might be the problem? I've tried setting the data source after the initialize component method but this results in a different error as the unit type is null..
The type of data source is set in a property for the control and below is the relevant code
form.Designer.cs (this is generated for you not a custom cs file called designer)
//
// cmbWheelUnitCR
//
this.cmbWheelUnitCR.DataSource = ((object)(resources.GetObject("cmbWheelUnitCR.DataSource")));
this.cmbWheelUnitCR.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cmbWheelUnitCR.FormattingEnabled = true;
this.cmbWheelUnitCR.Items.AddRange(new object[] {
"mm",
"yd"});
My custom combobox
public string UnitType
{
get { return m_unitType; }
set { m_unitType = value;
this.DataSource = Units.Instance.UnitTypes(m_unitType);}
}
public UnitComboBox()
{
InitializeComponent();
}
I was able to solve this accidentally by setting the data source within an override onLoad event..

Designer serialization and collection

I have an custom control that represents a grid; and implements another custom control.
When opening this control in de designer, I am able to use the collection editor to set my collection. When saving; the designer successfully saves my collection.
However, when dropping this control on a form; it still (and should) expose(s) the collection property allowing me to modify the default values as i have defined in the other control.
However; when saving this designer; it also tries to store the predefined items in the collection; adding the default ones with every save.
What is the best way to solve this problem? I have attached a code sample.
Code sample where i have defined my collection:
GridPicture.cs
[Category("Layout")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Editor(typeof(CollectionEditor), typeof(UITypeEditor))]
public GridPictureColumnDefinitionCollection ColumnDefinitions
{
// The DesignerSerializationVisibility attribute instructs the design editor to serialize the contents of the collection to source code.
// This will place all the code required to add the items to a collection variable of GridPictureColumnDefinitionCollection.
get
{
return m_ColumnDefinitions;
}
}
Generated designer code of my first 'implementation' of this grid; Picture1.cs
VGTest.GridPictureColumnDefinition gridPictureColumnDefinition1 = new VGTest.GridPictureColumnDefinition();
VGTest.GridPictureColumnDefinition gridPictureColumnDefinition2 = new VGTest.GridPictureColumnDefinition();
VGTest.GridPictureRowDefinition gridPictureRowDefinition1 = new VGTest.GridPictureRowDefinition();
gridPictureColumnDefinition1.Auto = true;
gridPictureColumnDefinition1.Value = 0F;
gridPictureColumnDefinition2.Auto = true;
gridPictureColumnDefinition2.Value = 0F;
this.ColumnDefinitions.Add(gridPictureColumnDefinition1);
this.ColumnDefinitions.Add(gridPictureColumnDefinition2);
gridPictureRowDefinition1.Auto = true;
gridPictureRowDefinition1.Value = 0F;
this.RowDefinitions.Add(gridPictureRowDefinition1);
Code sample when i place this picture1 on another picture; picture2.cs: (Note that picture11 is the picture1, as it is the 1st of picture1 ;)
VGTest.GridPictureColumnDefinition gridPictureColumnDefinition1 = new VGTest.GridPictureColumnDefinition();
VGTest.GridPictureColumnDefinition gridPictureColumnDefinition2 = new VGTest.GridPictureColumnDefinition();
VGTest.GridPictureColumnDefinition gridPictureColumnDefinition3 = new VGTest.GridPictureColumnDefinition();
VGTest.GridPictureRowDefinition gridPictureRowDefinition1 = new VGTest.GridPictureRowDefinition();
// Some code removed that does the Auto and Value settings as above
this.picture11.ColumnDefinitions.Add(gridPictureColumnDefinition1);
this.picture11.ColumnDefinitions.Add(gridPictureColumnDefinition2);
this.picture11.ColumnDefinitions.Add(gridPictureColumnDefinition3);
The picture2 control; when it regenerates the InitializeComponent() method; now adds the columndefinitions which i have added in picture1.
I have written this temporary fix for the problem:
{
// This makes sure column definitions are only serialized when configured at a implementation of this GridPicture.
// This is a Quick/Dirty fix for the following problem:
// When MyPanel (:GridPicture) is put on PanelContainer(:Picture); the picture designer (re)serializes this each save.
return this.GetType().BaseType.Name == typeof(Picture).Name;
}
I was unable to find a better method. I have decided to stick with the solution below.
{
// This makes sure column definitions are only serialized when configured at a implementation of this GridPicture.
// This is a Quick/Dirty fix for the following problem:
// When MyPanel (:GridPicture) is put on PanelContainer(:Picture); the picture designer (re)serializes this each save.
return this.GetType().BaseType.Name == typeof(Picture).Name;
}

Categories

Resources