I'm working with OxyPlot in WPF. Trying to display PlotModels in a UI.
Sometimes the plot is displayed in the UI and sometimes not.
This code is used to initialize the PlotModel by a BackgroundWorker:
ViewModel:
this.pm = setUpModel();
data2plot();
With the methods:
private PlotModel _pm;
public PlotModel pm
{
get { return _pm; }
set { _pm= value; RaisePropertyChanged("pm"); }
}
private PlotModel setUpModel()
{
PlotModel pm = new PlotModel();
pm.IsLegendVisible = false;
var xAxis= new CategoryAxis();
pm.Axes.Add(xAxis);
var valueAxis = new LinearAxis();
pm.Axes.Add(valueAxis);
return pm ;
}
//tbl holds the data in the second column
private void data2plot(DataTable tbl)
{
var series = new OxyPlot.Series.LineSeries();
int i = 0;
foreach (DataRow row in tbl.Rows)
{
double val = double.Parse(row[1].ToString());
series.Points.Add(new DataPoint(i, val));
i++;
}
this.pm.Series.Add(series);
}
Code used in View to initialize the model:
<Grid HorizontalAlignment="Left" Height="205" Margin="10,117,0,0" VerticalAlignment="Top" Width="308">
<oxypl:PlotView x:Name="plot" Model="{Binding pm,UpdateSourceTrigger=PropertyChanged}" Margin="10" Grid.Row="1">
</oxypl:PlotView>
</Grid>
with the following references
xmlns:oxypl="http://oxyplot.org/wpf"
DataContext="{Binding Source={StaticResource Locator}, Path=ViewModel}"
The Binding works, so I guess there have to be a problem with my PlotModel.
I don't know if this is relevant, but the DataTable tbl is initialized by the Lazy<T> class (with isThreadSafe=true).
When you add to your pm member, the value of pm is not being updated - it's the content of that member that is updated, i.e. the setter is never called, so the RaisePropertyChanged event is never emitted and the view isn't notified of any change. Suggest you build a local set of data and then overwrite pm with a complete set of data at the end, or emit a RaisePropertyChanged event when you're done.
The reason that it is working sometimes I would guess is that the first (and only) time the view reads the content back from the viewmodel you've already populated it, so you see something, but then no updates.
You also have to call
pm.InvalidatePlot(true);
after adding to the series, in order to trigger a redraw.
Related
I have a TextBox in a Windows Desktop WPF application bound to a property of a ViewModel. Now the user focuses the TextBox and starts entering a new value. During this time a background process gets a new Value for the same Property (e.g. because another user in a multi user environment enters a new value and an observer is detecting and propagating this change) and calls a PropertyChanged event for this Property. Now the value changes and the stuff the current user just entered is lost.
Is there a built in way to prevent the change while the TextBox is focused? Or do I have to build my own solution?
I think a custom control is needed to achieve the behavior you describe. By overriding a couple methods on the default WPF TextBox, we can keep the user input even if the View Model changes.
The OnTextChanged method will be called regardless of how our textbox is updated (both for keyboard events and View Model changes), but overriding the OnPreviewKeyDown method will separate out direct user-input. However, OnPreviewKeyDown does not provide easy access to the textbox value because it is also called for non-printable control characters (arrow keys, backspace, etc.)
Below, I made a WPF control that inherits from TextBox and overrides the OnPreviewKeyDown method to capture the exact time of the last user key-press. OnTextChanged checks the time and updates the text only if both events happen in quick succession.
If the last keyboard event was more than a few milliseconds ago, then the update probably did not happen from our user.
public class StickyTextBox : TextBox
{
private string _lastText = string.Empty;
private long _ticksAtLastKeyDown;
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
_ticksAtLastKeyDown = DateTime.Now.Ticks;
base.OnPreviewKeyDown(e);
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
if (!IsInitialized)
_lastText = Text;
if (IsFocused)
{
var elapsed = new TimeSpan(DateTime.Now.Ticks - _ticksAtLastKeyDown);
// If the time between the last keydown event and our text change is
// very short, we can be fairly certain than text change was caused
// by the user. We update the _lastText to store their new user input
if (elapsed.TotalMilliseconds <= 5) {
_lastText = Text;
}
else {
// if our last keydown event was more than a few seconds ago,
// it was probably an external change
Text = _lastText;
e.Handled = true;
}
}
base.OnTextChanged(e);
}
}
Here's a sample View Model which I used for testing. It updates its own property 5 times from a separate thread every 10 seconds to simulate a background update from another user.
class ViewModelMain : ViewModelBase, INotifyPropertyChanged
{
private delegate void UpdateText(ViewModelMain vm);
private string _textProperty;
public string TextProperty
{
get { return _textProperty; }
set
{
if (_textProperty != value)
{
_textProperty = value;
RaisePropertyChanged("TextProperty");
}
}
}
public ViewModelMain()
{
TextProperty = "Type here";
for (int i = 1; i <= 5; i++)
{
var sleep = 10000 * i;
var copy = i;
var updateTextDelegate = new UpdateText(vm =>
vm.TextProperty = string.Format("New Value #{0}", copy));
new System.Threading.Thread(() =>
{
System.Threading.Thread.Sleep(sleep);
updateTextDelegate.Invoke(this);
}).Start();
}
}
}
This XAML creates our custom StickyTextBox and a regular TextBox bound to the same property to demonstrate the difference in behavior:
<StackPanel>
<TextBox Text="{Binding TextProperty, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<TextBlock FontWeight="Bold" Margin="5" Text="The 'sticky' text box">
<local:StickyTextBox Text="{Binding TextProperty}" MinWidth="200" />
</TextBlock>
</StackPanel>
Hope you can help me whit this :)
I WANT TO: give the following values to four different radio buttons: 30, 50, 100 and 200 (doesn't really matter what the values is for now). Right now I need to go into the code and change the number my self. I want these radio buttons to do the job when checked.
I will paste the code here. Can you please be very spesific when explaining this to me (if you can and bother to do so).
Thank you!
//Method for establishing connection to database.
// Sette parameter for limit
public static MongoDatabase GetDatabase(string searchText)
{
/* try
{*/
TweetOC.Clear();
MongoServerSettings settings = new MongoServerSettings();
settings.Server = new MongoServerAddress("xxxx", xxxx);
MongoServer server = new MongoServer(settings);
MongoDatabase database = server.GetDatabase("tweet_database");
var collection = database.GetCollection<Tweets>("docs");
System.Console.WriteLine("5");
var query = Query.And(Query.Matches("text", searchText),
Query.NE("geo_enabled", false));
System.Console.WriteLine("6");
//var match = Query.ElemMatch("text", query);
var cursor = collection.Find(query);
cursor.SetLimit(30);
System.Console.WriteLine("7");
//Puts the result from the last query into a list.
var resultList = cursor.ToList();
//Iterates over the previous mentioned list and inserts the content into the ObservableCollcetion created earlier.
foreach (var item in resultList)
TweetOC.Add(item);
System.Console.WriteLine(TweetOC.Count);
return database;
}
I HAVE TO CHANGE CURSOR.LIMIT(manually) ALL THE TIME. I WANT THIS NUMBER TO CHANGE AUTOMATICALLY WHEN A RADIO BUTTON IS CHECKED.
THE METHOD AND XAML WILL FOLLOW:
// I want this to be if radiobutton is 20, then this should be sent to cursor.set limit. I cannot make another string in database.cs without getting an error.
/* Private void RadioButton_Checked(object sender, RoutedEventArgs e)
{
var radioButton = sender as RadioButton;
if (radioButton == 20)
return;
int intIndex = Convert.ToInt32(radioButton.Content.ToString(Cursor.SetLimit));
}
* Remember Checked="RadioButton_Checked" in the XAML if you want to try
*/
XAML for one of the four buttons:
<RadioButton Content="RadioButton" Grid.Column="2" HorizontalAlignment="Left"
Margin="20,116,0,0" Grid.Row="2" VerticalAlignment="Top"/>
HOW SHOULD THIS ACTUALLY LOOK TO GET IT WORKING? PLEASE EDIT THE CODE (if you bother) SO I CAN SEE AND UNDERSTAND THIS.
THANKS AGAIN!
To do this in an MVVM friendly way, bind the IsChecked property like this:
IsChecked="{Binding Path=CursorLimit, Converter={StaticResource ParamToIntConverter}, ConverterParameter=10}"
Set the parameter to the correct value for the given radio button, of course. If you are not familiar with converters, you need a line in your resources like (assuming you have the local xmlns set up to point to the converter namespace):
<local:ParamToIntConverter x:Key="ParamToIntConverter"/>
Then your converter looks like:
public class ParamToIntConverter : IValueConverter
{
public object Convert (...)
{
return value.Equals(int.Parse((string)parameter));
}
public object ConvertBack(...)
{
if ((bool)value)
return int.Parse((string)parameter);
else
return Binding.DoNothing;
}
}
Your xmal code:
<RadioButton Content="RadioButton" Grid.Column="2" HorizontalAlignment="Left"
Margin="20,116,0,0" Grid.Row="2" VerticalAlignment="Top" Checked="RadioButton_Checked" />
Your *.cs code:
private void RadioButton_Checked(object sender, RoutedEventArgs e)
{
// ... do what you need for this button
}
Use different Checked="[insert_method_name]" template with equal method names in your *.cs file and do what you need in each method.
Also you can try to make it as in this http://www.dotnetperls.com/radiobutton-wpf example.
I have this very straightforward code:
var xlData = Excel8OleDbHelper.ImportExcelFile(fileName);
var viewModel = new MyViewModel(xlData);
_window = new MyWindow(viewModel);
_window.ShowDialog();
The Excel8OleDbHelper.ImportExcelFile() method works, I can view the DataTable's content when debugging. The problem is that _window simply does not show up and the thread behaves as if it did (i.e. it's waiting for the unshown window to be closed).
If I change the code for this:
var viewModel = new MyViewModel(new DataTable());
_window = new MyWindow(viewModel);
_window.ShowDialog();
Then the window appears, but then of course with an empty grid.
The XAML for the grid:
<DataGrid x:Name="MyGrid" Margin="4"
IsReadOnly="True" SelectionUnit="Cell"
ItemsSource="{Binding Path=GridSource}" />
And the ViewModel's constructor:
public DataView GridSource { get; private set; }
public MyViewModel(DataTable dataSource)
{
GridSource = dataSource.DefaultView;
}
This is my first time using a WPF data grid so maybe I'm doing something wrong here, but I really don't see how something wrong with the grid could prevent the window from showing up at all, without giving me an exception and actually freezing my app.
Any clues? I'll gladly supply more code if anything is missing!
UPDATE
Loading an Excel workbook with 116 rows x 47 columns works; the file I need to load has 5,000+ rows x 47 columns - could it be that the WPF DataGrid requires paging for larger datasets? The WinForms DataGridView had no issues with it and was much faster, I assumed its WPF counterpart would work in a similar fashion. So the problem was that ShowDialog was waiting for the data grid to load the data. I assumed it was hung because I didn't expect the WPF DataGrid to take over a minute to do what the WinForms DataGridView did instantly.
Now that we have identified that the issue with the volume of the data:
Do you have row and column virtualization turned on ? You can set EnableRowVirtualization=true on the datagrid
Are you using auto-sized columns ? Recomputing optimal size for every new row/column can be a perf drag - Try using a fixed column size or a proportional (1*) size. You can try a similar approach with Height for Rows.
Try doing this. I am not sure this will work or not. I think loading Excel in main is causing problem or deadlocks.
var xlData = Excel8OleDbHelper.ImportExcelFile(fileName);
var viewModel = new MyViewModel(fileName);
_window = new MyWindow(viewModel);
_window.ShowDialog();
public class MyViewModel
{
public MyViewModel(string filename)
{
filename = filename;
}
private string _filename;
private DataTable _xlData;
public DataTable XlData
{
return _xlData ?? (_xlData = Excel8OleDbHelper.ImportExcelFile(fileName);
}
}
Edit: I have already looked at the Infragistics forums. The code below is based on their samples. The data binding just doesn't seem to work.
I have an Infragistics XamDataGrid in my view.
<DockPanel>
<grid:XamDataGrid x:Name="gridData" DataContext="{Binding Path=DataEditorDataTable}" DataSource="{Binding Path=DataEditorDataTable.DefaultView}"
IsSynchronizedWithCurrentItem="True" Visibility="{Binding DataGridVisible}">
<grid:XamDataGrid.FieldLayoutSettings>
<grid:FieldLayoutSettings AutoGenerateFields="True" AllowAddNew="False" AllowDelete="False" />
</grid:XamDataGrid.FieldLayoutSettings>
</grid:XamDataGrid>
</DockPanel>
I set the data context for the user control in the constructor.
public DataEditor(SomeDataType DataType, IEventAggregator eventaggregator)
{
InitializeComponent();
this.DataContext = new DataEditorViewModel(DataType, eventaggregator);
}
In the dataeditor view model, I subscribe to an event that lets me know when data has changed and I build a datatable and call a method SetData. (I cannot know ahead how many columns of data are going to be shown in the grid, and these columns keep changing with user interaction, so I am hoping to use the data table to bind.)
I assign the properties in a method like so.
/// <summary>
/// Returns the data that the data editor displays.
/// </summary>
public DataTable DataEditorDataTable
{
get
{
return dtDataEditor;
}
set
{
dtDataEditor = value;
OnPropertyChanged("DataEditorDataTable");
}
}
/// <summary>
/// Method to set data on load
/// </summary>
private void SetData(DataTable dtDataEditor)
{
if (!isDataEditorCellEdited)
if (dtDataEditor != null && dtDataEditor.Rows.Count > 0)
{
try
{
//Assign the data to the grid
DataEditorDataTable = dtDataEditor;
DataGridVisible = Visibility.Visible;
}
catch
{
//If any exception occurs, hide the grid
DataGridVisible = Visibility.Collapsed;
}
}
else
//If no data, hide the grid
DataGridVisible = Visibility.Collapsed;
}
The problem is that the binding is simply not happening. Is there anything in particular I have missed with regard to the bindings?
For debugging the binding errors you should look at the output window in visual studio to see if there are any errors.
Reading the code that you have, I assume that the binding is incorrect and should be:
DataContext="{Binding Path=DataEditorDataTable}" DataSource="{Binding Path=DefaultView}"
The change that I made was to remove the property from the table from the path of the DataSource since the table is already the DataContext and you want to bind to the DefaultView off of the table.
So I'm trying to update my RadRadialGauge. It will display data (using an animated Needle) that is being retrieved on a real-time basis. I have a RadChartView that currently works by using TimeStamp and Value properties to draw the Chart. When I add a chart, sometimes I might want to add a few based on the variable I'm watching. For instance, if I want to watch Motor Speed and Output Frequency, I have to add multiple vertical axes. Part of my code to handle the data binding for the RadChartView is here:
var lineSeries = new LineSeries();
lineSeries.CategoryBinding =
new PropertyNameDataPointBinding() { PropertyName = "TimeStamp" };
lineSeries.ValueBinding =
new PropertyNameDataPointBinding() { PropertyName = "Value" };
lineSeries.ItemsSource = (chart.Chart as GuiAnalogQueue).Records;
The rest of the code is just appearance handling, and then at the end I add the LineSeries to my RadChartView.
Now, I'm trying to port this code, in a way, over to RadGauge. I'm not sure how to bind the values to the Needle so the needle moves when the Value changes.
In the XAML I've tried Value="{Binding Value}" I've tried adding binding to the ValueSource varible. Also I have done needle.Value = chart.Chart.Value;
I can't figure it out, so any help is appreciated.
UPDATE
This is what I'm trying to accomplish. My Records collection has two properties, Value and TimeStamp. I'm trying to bind my Value in the Records to the needle Value. This is my approach to do it programmatically:
public void InitializeCharts(ChartsVM charts, Theme theme)
{
DataContext = charts;
foreach (cVM chart in charts.Charts)
{
Binding value = new Binding();
value.Source = (chart.Chart as GuiAnalogQueue).Records;
value.Path = new PropertyPath("Value");
needle.SetBinding(Needle.ValueProperty, value);
}
}
However, when I do this, it is not changing the needle.Value at all. My Records is the collection that uses NotifyPropertyChanged("Records"), so I would expect my needle to change everytime Records is changed.
As you see in my original post, those three lines take care of binding the variables to ChartView charts, however I can't get the RadGauge to work.
In short, I found that Needle's don't use any type of collections for their Values. So when I tried setting up a Source to be inside of a collection, and a Path, it wasn't really liking that. Instead, I added a property right before I add the Value to the records collection (in my update values function). That way I could set my binding up as:
Binding value = new Binding();
value.Source = (chart.Chart as GuiAnalogQueue);
value.Path = new PropertyPath("AnalogValue");
needle.SetBinding(Needle.ValueProperty, value);
That reads as, the Needle will bind its Value property with the AnalogValue property that is in the Source--chart.Chart as GuiAnalogQueue.
Hope this helps if you've been directed to this page.
Here is a basic example of using the RadRadialGauge.
XAML:
<telerik:RadRadialGauge x:Name="radialGauge"
Width="300"
Height="300"
Grid.Column="0">
<telerik:RadialScale Min="1"
Max="12">
<telerik:RadialScale.Indicators>
<telerik:Needle x:Name="needle" />
<telerik:Pinpoint/>
</telerik:RadialScale.Indicators>
</telerik:RadialScale>
</telerik:RadRadialGauge>
As you can see i have a radial gauge with a radial scale defined. Radial Scale has a needle as the indicator. The RadialScale is from 1 to 12. Note that i have given a name for the needle. We will use this to push values from the code behind.
In this example i am using a dispatcher timer to tick every 1 second and i am generating a random value between 1 to 12. Here is the code behind snippets.
code snippet:
Following variables are declared at the window level
TimeSpan interval = TimeSpan.FromSeconds(1);
DispatcherTimer timer;
Random rnd = new Random();
I have defined event handlers for Window Loaded & Unloaded events. On Window Load, i start the timer.
void OnWindowLoad(object sender, RoutedEventArgs e)
{
timer = new DispatcherTimer();
timer.Interval = interval;
timer.Tick += new EventHandler(Timer_Tick);
timer.Start();
}
Here is the timer tick function:
private void Timer_Tick(object sender, EventArgs e)
{
timer.Stop();
SetNextValue();
timer.Start();
}
Here is the SetNextValue function:
private void SetNextValue()
{
int minValue = 1;
int maxValue = 12;
int nextValue = rnd.Next(minValue, maxValue);
needle.Value = nextValue;
}
In the Unloaded event handler i am stopping the timer.
void OnWindowUnload(object sender, RoutedEventArgs e)
{
timer.Stop();
}
Output:
when you run the app, you will see the needle changing its position because we are generating random numbers from 1 to 12 every second and we set the generated number to needle's value. The SetNextValue() method can be your gateway to monitoring the real value and set the needle value to that real data.
This is the basic example code i can think of to explain the radial gauge.
Hope this provides the answer you are looking for.
Update:
Here is an MVVM way of setting the needle value. Let the window implement INotifyPropertyChanged interface. Set the datacontext to the window itself
public partial class MainWindow : Window, INotifyPropertyChanged
public MainWindow()
{
InitializeComponent();
Loaded += OnWindowLoad;
Unloaded += OnWindowUnload;
DataContext = this;
}
Provide implementation for the PropertyChanged event like below:
public event PropertyChangedEventHandler PropertyChanged = delegate { };
void NotifyPropertyChanged(string propName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
Implement a property called NeedleValue.
int needleValue = 1;
public int NeedleValue
{
get
{
return needleValue;
}
set
{
needleValue = value;
NotifyPropertyChanged("NeedleValue");
}
}
In the SetNextValue - just set the newly created NeedleValue property. this will fire the property changed notification.
private void SetNextValue()
{
int minValue = 1;
int maxValue = 12;
int nextValue = rnd.Next(minValue, maxValue);
NeedleValue = nextValue;
}
In the XAML bind the Needle Value property to NeedleValue like below
<telerik:Needle x:Name="needle" Value="{Binding NeedleValue}" />
Hope this provides you with the answer you are looking for :)
Lohith (Tech Evangelist, Telerik India)