My existing c# WPF project with OxyPlot.Wpf v2.0 NuGet Package runs fine and uses Data binding.
OxyPlot.Wpf 2.1.2 is out but I have problems that the data binding no longer processes updates for use as Live-Chart.
To test, I wrote 2 test programs to clarify that.
In both versions INotifyPropertyChanged is implemented and I use the following properties for my data:
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
...
private ObservableCollection<DataPoint> valueList1;
public ObservableCollection<DataPoint> ValueList1
{
get => valueList1;
set
{
valueList1 = value;
OnPropertyChanged();
}
}
private ObservableCollection<DataPoint> valueList2;
public ObservableCollection<DataPoint> ValueList2
{
get => valueList2;
set
{
valueList2 = value;
OnPropertyChanged();
}
}
In the constructor, I add a few test data that can also be seen directly in the OxyPlot Chart when it starts in both OxyPlot versions. So the data binding should be fine with that.
I add further data to the ObservableCollection Propertys by clicking a button.
In Version 2.0 the WPF-Chart updates and shows the new Data.
In Version 2.1 nothing happens, data binding doesn't seem to trigger any updates...
As part of the update to OxyPlot.Wpf v2.1, I have to move my XAML code to the code behind:
XAML v2.0
<oxy:Plot Title="OxyTest" >
<oxy:Plot.Axes>
<oxy:LinearAxis MaximumPadding="0.1" IsZoomEnabled="True" MajorStep="100" />
</oxy:Plot.Axes>
<oxy:Plot.Series>
<oxy:AreaSeries Color="Red" Title="DataLine1" ItemsSource="{Binding Path=ValueList1}" />
<oxy:LineSeries Color="Blue" Title="DataLine2" ItemsSource="{Binding Path=ValueList2}" />
</oxy:Plot.Series>
</oxy:Plot>
Code Behind v2.1
MyModel = new PlotModel
{
Title = "OxyTest",
};
MyModel.Axes.Add(new LinearAxis
{
MaximumPadding = 0.1,
IsZoomEnabled = true,
MajorStep = 100
});
MyModel.Series.Add(new AreaSeries
{
Color = OxyColor.FromRgb(255, 0, 0),
Title = "DataLine1",
ItemsSource = ValueList1
});
MyModel.Series.Add(new LineSeries
{
Color = OxyColor.FromRgb(0, 0, 255),
Title = "DataLine2",
ItemsSource = ValueList2
});
EDIT:
The InvalidatePlot() method must be called for the display to be updated. The method has existed for a long time but in version 2.0 I didn't have to trigger it manually!?
Thanks to this Article How to refresh oxyplot plot when data changes
The Solution from heltonbiker looks good so i added this in my project:
ValueList1.CollectionChanged += (a, b) => MyModel.InvalidatePlot(true);
ValueList2.CollectionChanged += (a, b) => MyModel.InvalidatePlot(true);
Maybe this will help someone else.
The offical OxyPlot documentation is labeld as under construction.
EDIT2:
I found another Solution for my DataBinding Problem.
Just add OxyPlot.Contrib.Wpf to the Project and change the XMLNS from "http://oxyplot.org/wpf" to "http://oxyplot.org/wpf/contrib" and use the Exact same XAML from v2.0. No need to trigger the CollectionChanged Event. Databinding works fine.
Related
I have a form that has a dynamic amount of datagrids that are brought in programmatically each one on a new tabpage.
My problem is that I need to change the Header of each column. I have tried doing it through a method
DataGridForSupplier.Columns[0].Header = "123";
but that keeps crashing with an error:
Index was out of range. Must be non-negative and less than the size of the collection
Turns out the problem is that the grid wasn't finished loading. So after waiting for all tabpage to load and add data to all the grids , even then the code
DataGridForSupplier.Columns[0].Header = "123";
would still crash. If the tabs are left to load on their own with no header tampering then the datagrid shows fine.
I would just LOVE to do this in XAML problem is that seeing that I don't know how many grids will load at run time I tried doing this at the back. So I'm open to any solution at this point. I tried finding a solution that would incorporate something that would 'theme' all the datagrids. Luckily all the datagrids headers will repeat across all tabs. So header 1 on tabpage 1 - 10 will be the same. Header 2 on tabpage 1 - 10 will be the same
Something like
<DataGridTemplateColumn.Header>
<TextBlock Text="{Binding DataContext.HeaderNameText, RelativeSource=>> RelativeSource AncestorType={x:Type DataGrid}}}" />
</DataGridTemplateColumn.Header>
but this needs to repeat for every Grid. This seems to escape me at the moment.
Any help would be welcome.
A rather lengthy answer, but this solution does not require any additional libraries, 3rd party tools, etc. You can expand it as you want later such as for adding hooks to mouse-move/over/drag/drop/focus, etc. First the premise on subclassing which I found out early in my learning WPF. You can not subclass a xaml file, but can by a .cs code file. In this case, I subclassed the DataGrid to MyDataGrid. Next, I created an interface for a known control type to ensure contact of given functions/methods/properties. I have stripped this version down to cover just what you need to get.
The interface below is just to expose any class using this interface MUST HAVE A METHOD called MyDataGridItemsChanged and expects a parameter of MyDataGrid.. easy enough
public interface IMyDataGridSource
{
void MyDataGridItemsChanged(MyDataGrid mdg);
}
Now, declaring in-code a MyDataGrid derived from DataGrid. In this class, I am adding a private property of type IMyDataGridSource to grab at run-time after datagrids are built and bound.
public class MyDataGrid : DataGrid
{
// place-holder to keep if so needed to expand later
IMyDataGridSource boundToObject;
public MyDataGrid()
{
// Force this class to trigger itself after the control is completely loaded,
// bound to whatever control and is ready to go
Loaded += MyDataGrid_Loaded;
}
private void MyDataGrid_Loaded(object sender, RoutedEventArgs e)
{
// when the datacontext binding is assigned or updated, see if it is based on
// the IMyDataGridSource object. If so, try to type-cast it and save into the private property
// in case you want to add other hooks to it directly, such as mouseClick, grid row changed, etc...
boundToObject = DataContext as IMyDataGridSource;
}
// OVERRIDE the DataGrid base class when items changed and the ItemsSource
// list/binding has been updated with a new set of records
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
// do whatever default behavior
base.OnItemsChanged(e);
// if the list is NOT bound to the data context of the IMyDataGridSource, get out
if (boundToObject == null)
return;
// the bound data context IS of expected type... call method to rebuild column headers
// since the "boundToObject" is known to be of IMyDataGridSource,
// we KNOW it has the method... Call it and pass this (MyDataGrid) to it
boundToObject.MyDataGridItemsChanged(this);
}
}
Next into your form where you put the data grid. You will need to add an "xmlns" reference to your project so you can add a "MyDataGrid" instead of just "DataGrid". In my case, my application is called "StackHelp" as this is where I do a variety of tests from other answers offered. The "xmlns:myApp" is just making an ALIAS "myApp" to the designer to it has access to the classes within my application. Then, I can add
<Window x:Class="StackHelp.MyMainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:myApp="clr-namespace:StackHelp"
Title="Main Window" Height="700" Width="900">
<StackPanel>
<!-- adding button to the main window to show forced updated list only -->
<Button Content="Refresh Data" Width="100"
HorizontalAlignment="Left" Click="Button_Click" />
<myApp:MyDataGrid
ItemsSource="{Binding ItemsCollection, NotifyOnSourceUpdated=True}"
AutoGenerateColumns="True" />
</StackPanel>
</Window>
Now, into the MyMainWindow.cs code-behind
namespace StackHelp
{
public partial class MyMainWindow : Window
{
// you would have your own view model that all bindings really go to
MyViewModel VM;
public MyMainWindow()
{
// Create instance of the view model and set the window binding
// to this public object's DataContext
VM = new MyViewModel();
DataContext = VM;
// Now, draw the window and controls
InitializeComponent();
}
// for the form button, just to force a refresh of the data.
// you would obviously have your own method of querying data and refreshing.
// I am not obviously doing that, but you have your own way to do it.
private void Button_Click(object sender, RoutedEventArgs e)
{
// call my viewmodel object to refresh the data from whatever
// data origin .. sql, text, import, whatever
VM.Button_Refresh();
}
}
}
Finally to my sample ViewModel which incorporates the IMyDataGridSource
public class MyViewModel : IMyDataGridSource, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
public ObservableCollection<OneItem> ItemsCollection { get; set; }
= new ObservableCollection<OneItem>();
public void Button_Refresh()
{
ItemsCollection = new ObservableCollection<OneItem>
{
new OneItem{ DayName = "Sunday", DayOfWeek = 0},
new OneItem{ DayName = "Monday", DayOfWeek = 1},
new OneItem{ DayName = "Tuesday", DayOfWeek = 2},
new OneItem{ DayName = "Wednesday", DayOfWeek = 3},
new OneItem{ DayName = "Thursday", DayOfWeek = 4},
new OneItem{ DayName = "Friday", DayOfWeek = 5 },
new OneItem{ DayName = "Saturday", DayOfWeek = 6 }
};
RaisePropertyChanged("ItemsCollection");
}
// THIS is the magic hook exposed that will allow you to rebuild your
// grid column headers
public void MyDataGridItemsChanged(MyDataGrid mdg)
{
// if null or no column count, get out.
// column count will get set to zero if no previously set grid
// OR when the items grid is cleared out. don't crash if no columns
if (mdg == null)
return;
mdg.Columns[0].Header = "123";
}
}
Now, taking this a step further. I don't know how you manage your view models and you may have multiple grids in your forms and such. You could create the above MyViewModel class as a smaller subset such as MyDataGridManager class. So each datagrid is bound to its own MyDataGridManager instance. It has its own querying / populating list for the grid, handling its own rebuild column headers, mouse clicks (if you wanted to expand), record change selected, etc.
Hope this helps you some. Again, this does not require any other 3rd party libraries and you can extend as you need. I have personally done this and more to the data grid and several other controls for certain specific pattern handling.
I am currently working on a WPF project with the Telerik FW.
During the runtime I am getting the following warning:
System.Windows.Freezable Warning: 1 : CanFreeze is returning false because a DependencyProperty on the Freezable has a value that is an expression; Freezable='System.Windows.Media.TranslateTransform'; Freezable.HashCode='36319496'; Freezable.Type='System.Windows.Media.TranslateTransform'; DP='X'; DpOwnerType='System.Windows.Media.TranslateTransform'
This is my xaml code
<Style x:Key="PieSliceStyle" TargetType="Path">
<Setter Property="Fill" Value="{Binding DataItem.Color}" />
</Style>
<telerik:PieSeries ItemsSource="{Binding Source}" DefaultSliceStyle="{StaticResource PieSliceStyle}">
<telerik:PieSeries.ValueBinding>
<telerik:PropertyNameDataPointBinding PropertyName="Value" />
</telerik:PieSeries.ValueBinding>
<telerik:PieSeries.LabelDefinitions>
<telerik:ChartSeriesLabelDefinition Margin="-10">
<telerik:ChartSeriesLabelDefinition.Binding>
<telerik:PropertyNameDataPointBinding PropertyName="Label" />
</telerik:ChartSeriesLabelDefinition.Binding>
</telerik:ChartSeriesLabelDefinition>
</telerik:PieSeries.LabelDefinitions>
</telerik:PieSeries>
And this is some part of my ViewModel
private readonly SolidColorBrush PieColorEnableSlice = new SolidColorBrush(Colors.LightGray);
private readonly SolidColorBrush PieColorDisabledSlice = new SolidColorBrush(Colors.Red);
public AsyncObservableCollection<MSShareClassModel> List
{
get
{
return this._list;
}
set
{
if (this.SetProperty(ref this._list, value, "List"))
{
this.Source = new AsyncObservableCollection<PieChartModel>
{
new PieChartModel
{
Label = "Active",
Value = this._list.Count(x => x.Status == "1"),
Color = this.PieColorEnableSlice
},
new PieChartModel
{
Label = "Disable",
Value = this._list.Count(x => x.Status == "0"),
Color = this.PieColorDisabledSlice
},
};
}
}
}
I think one solution would be to create the corlor directly from the xaml source.
But I want to keep this binding to be able to change the color programatically.
Any idea on this warning?
Ok After more investigation, this is not comming from the pieSeries..
This is fired by the RadGridView....
I removed all the xaml components one by one from the xaml.
The last one alive was the grid and I keeped having that warning.
I removed the grid and enabled all the others components, and the warning was gone until I uncomment the grid in the xaml.
Nothing fancy, just the simple RadGridView declaration. No DataSource or Column definition, just a simple empty grid.
This issue seems to have been declared to telerik dev team since 2010 from many components. (Treeviews, grid etc...)
And after some reading, Telerik will not fix this kind of issue... (we can vote for it on http://feedback.telerik.com/ :) )
I will not make this as an answer, I still have the warning =/
In my C# WPF application I have a DataGrid and right above it there is a TextBox for the user to search and filter the grid as they type. If the user types fast though, no text will appear until 2 seconds after they type because the UI thread is too busy updating the grid.
Since most of the delay is all on the UI side (i.e. filtering the datasource is nearly instant, but rebinding and re-rendering the grid is slow), multi-threading has not been helpful. I then tried setting the dispatcher of just the grid to be at a lower level while the grid gets updated, but this didn't solve the issue either. Here's some code... Any suggestions on how to solve this type of problem?
string strSearchQuery = txtFindCompany.Text.Trim();
this.dgCompanies.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(delegate
{
//filter data source, then
dgCompanies.ItemsSource = oFilteredCompanies;
}));
Using a ListCollectionView as your ItemsSource for the grid and updating the Filter works much faster than re-assigning the ItemsSource.
The example below filters 100000 rows with no apparent lag by simply refreshing the View in the setter for the search term text property.
ViewModel
class ViewModel
{
private List<string> _collection = new List<string>();
private string _searchTerm;
public ListCollectionView ValuesView { get; set; }
public string SearchTerm
{
get
{
return _searchTerm;
}
set
{
_searchTerm = value;
ValuesView.Refresh();
}
}
public ViewModel()
{
_collection.AddRange(Enumerable.Range(0, 100000).Select(p => Guid.NewGuid().ToString()));
ValuesView = new ListCollectionView(_collection);
ValuesView.Filter = o =>
{
var listValue = (string)o;
return string.IsNullOrEmpty(_searchTerm) || listValue.Contains(_searchTerm);
};
}
}
View
<TextBox Grid.Row="0" Text="{Binding SearchTerm, UpdateSourceTrigger=PropertyChanged}" />
<ListBox ItemsSource="{Binding ValuesView}"
Grid.Row="1" />
If you are targeting .net 4.5, an option is to set the Delay property on your TextBox which will prevent setting the source value until a certain time threshold is met (until the user stops typing).
<TextBox Text="{Binding SearchText, Delay=1000}"/>
This waits for 1 second after there is no user input to set the source value.
Another option is to have a button trigger your filter/search instead of when the textbox changes.
Im using WPFToolkit datagrid control and do the binding in such way:
<WpfToolkit:DataGrid x:Name="dgGeneral" SelectionMode="Single"
SelectionUnit="FullRow"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
Grid.Row="1" ItemsSource="{Binding Path=Conversations}" >
public List<CONVERSATION> Conversations
{
get { return conversations; }
set
{
if (conversations != value)
{
conversations = value;
NotifyPropertyChanged("Conversations");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public void GenerateData()
{
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = bw.WorkerReportsProgress = true;
List<CONVERSATION> list = new List<CONVERSATION>();
bw.DoWork += delegate { list = RefreshGeneralData(); };
bw.RunWorkerCompleted += delegate
{
try
{
Conversations = list;
}
catch (Exception ex)
{
CustomException.ExceptionLogCustomMessage(ex);
}
};
bw.RunWorkerAsync();
}
And than in the main window i call GenerateData() after setting DataCotext of the window to instance of the class, containing GenerateData().
RefreshGeneralData() returns some list of data i want and it returns it fast.
Overall there are near 2000 records and 6 columns(im not posting the code i used during grid's initialization, because i dont think it can be the reason) and the grid hangs for almost 10 secs!
---
PS i found the project, that is written in similar way, but even binding of 50000 records is done without UI hang there! What am i doing wrong?
UPDATE i think that's because of ScrollViewer, which contains the whole grid in it. But why?
I ran into a similar situation yesterday (that's how I found this post) and after much struggle, this is the lesson I learnt. I hope it helps others too.
The problem is that the width property of the container grid of the datagrid is set to Auto. My datagrid only has about 500 rows with 5 columns in each row, but in this Auto mode, it is hanging for ever. Once I changed it to * or a fixed width. It's up right away.
Unless you explicitely disable it, the items in the DataGrid are virtualized, i.e. only the items currently shown are rendered. You might be having a problem due to UI Automation (this was fixed in .NET 4). See http://wpf.codeplex.com/Thread/View.aspx?ThreadId=41964
This can happen if you have a Wacom tablet or a screen reader installed.
I am building a small wpf app in C#. When a button gets clicked a third
party dll function constructs a tree like object. This object is bound
to a treeview. This works fine but takes a bit of time to load. As the
dll function constructs the object it prints progress info to the
console. I want to redirect this into a TextBlock so that the user
gets to see the progress messages.
My window ctor looks like this:
InitializeComponent();
StringRedir s = new StringRedir(ref ProgressTextBlock);
Console.SetOut(s);
Console.SetError(s);
this.DataContext = s;
xaml:
<TextBlock Text="{Binding Path=Text}" Width="244"
x:Name="ProgressTextBlock" TextWrapping="Wrap" />
<TreeView >...</TreeView>
The StringRedir class is shown below. The problem is the TextBlock for
some reason does not get updated with the messages until the TreeView
gets loaded. Stepping through I see the Text property being updated
but the TextBlock is not getting refreshed. I added a MessageBox.Show
() at the point where Text gets updated and this seems to cause the
window to refresh each time and I am able to see each message. So I
guess I need some way to explicitly refresh the screen...but this
doesnt make sense I thought the databinding would cause a visual
refresh when the property changed. What am I missing here? How do I
get it to refresh? Any advice is appreciated!
public class StringRedir : StringWriter , INotifyPropertyChanged
{
private string text;
private TextBlock local;
public string Text {
get{ return text;}
set{
text = text + value;
OnPropertyChanged("Text");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public StringRedir(ref TextBlock t)
{
local = t;
Text = "";
}
public override void WriteLine(string x)
{
Text = x +"\n";
//MessageBox.Show("hello");
}
}
You haven't included the code that is loading the data for the TreeView, but I'm guessing it's being done on the UI thread. If so, this will block any UI updates (including changes to the TextBlock) until it has completed.
So after doing some reading on the WPF threading model ( http://msdn.microsoft.com/en-us/library/ms741870.aspx ) I finally got it to refresh by calling Dispatcher Invoke() with Dispatch priority set to Render. As Kent suggested above UI updates in the dispatcher queue were probably low priority. I ended up doing something like this.
XAML
<TextBox VerticalScrollBarVisibility="Auto"
Text="{Binding Path=Text, NotifyOnTargetUpdated=True}"
x:Name="test" TextWrapping="Wrap" AcceptsReturn="True"
TargetUpdated="test_TargetUpdated"/>
C# target updated handler code
private void test_TargetUpdated(object sender, DataTransferEventArgs e)
{
TextBox t = sender as TextBox;
t.ScrollToEnd();
t.Dispatcher.Invoke(new EmptyDelegate(() => { }), System.Windows.Threading.DispatcherPriority.Render);
}
Note: Earlier I was using a TextBlock but I changed to a TextBox as it comes with scrolling
I still feel uneasy about the whole flow though. Is there a better way to do this?
Thanks to Matt and Kent for their comments. If I had points would mark their answers as helpful.
I believe the problem is in the constructor of your StringRedir class. You're passing in ProgessTextBlock, and you're doing this to it:
local.Text = "";
This is effectively overwriting the previously set value for ProgressTextBlock.Text, which was this:
{Binding Text}
See what I mean? By explicitly setting a value to the TextBlock's Text property, you've cancelled the binding.
If I'm reading right, it looks like the idea of passing a TextBlock into the StringRedir's ctor is a hangover from before you tried binding directly. I'd ditch that and stick with the binding idea as it's more in the "spirit" of WPF.