How do I go about binding a chart series to a datasource where the data changes? I will of course need the chart series to reflect the changes to the underlying data. I need a fresh angle.
I think you mean charts from WPF Toolkit. Use ObservableCollection for add/remove events and INotifyPropertyChanged for update events.
Xaml:
<chart:Chart>
<chart:Chart.Series>
<chart:LineSeries DependentValuePath="Value" IndependentValuePath="Time"
Title="Example" x:Name="lineChart">
<chart:LineSeries.DependentRangeAxis>
<chart:LinearAxis Orientation="Y" Minimum="0" Maximum="20" />
</chart:LineSeries.DependentRangeAxis>
</chart:LineSeries>
</chart:Chart.Series>
</chart:Chart>
Code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var r = new Random();
var Items = new ObservableCollection<ChartItem>(
Enumerable.Range(0,5).Select(i=>new ChartItem(i,1)).ToList());
var t = new System.Timers.Timer { Interval = 1000 };
var start = DateTime.Now;
//occurs every 1 second
t.Elapsed += (s, e) => Dispatcher.Invoke((Action)(() =>
{
Items[r.Next(0, Items.Count)].Value = r.Next(20);
Items.Add(new ChartItem((e.SignalTime - start).TotalSeconds, r.Next(20)));
}));
t.Start();
lineChart.ItemsSource = Items;
}
}
Item class:
public class ChartItem:INotifyPropertyChanged
{
public ChartItem(double t, double v)
{
time = t;
myVar = v;
}
private double time;
public double Time
{
get { return time; }
set
{
time = value;
OnPropertyChanged("Time");
}
}
private double myVar;
public double Value
{
get { return myVar; }
set
{
myVar = value;
OnPropertyChanged("Value");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Also, dynamic charts: http://dynamicdatadisplay.codeplex.com/
Related
I have an ObservableCollection<Conversion> Queue, bound to ListBox control with ItemTemplate containing a TextBlock and a Button. When the button is clicked, a Win32 process starts. This process has an ErrorDataReceived event handler method which reads the process output and is supposed to update the PercentComplete property of the Conversion object in the collection. PercentComplete is bound to TextBlock's Text property.
How do I update PercentComplete from Win32 process event? I was hoping to pass the Conversion object to the ErrorDataReceived event handler, but the DataReceivedEventArgs only has a single Data property of type string.
Here is the code:
XAML:
<ListBox ItemsSource="{Binding Queue}" SelectedItem="{Binding SelectedItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding PercentComplete}" />
<Button Command="convertor:Commands.RunConversion">Run</Button>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code-behind:
private ObservableCollection<Conversion> _queue;
public ObservableCollection<Conversion> Queue
{
get { return _queue; }
set
{
_queue = value;
RaisePropertyChange("Queue");
}
}
private Conversion _selectedItem;
public Conversion SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
RaisePropertyChange("SelectedItem");
}
}
private void RunConversion_Executed(object sender, ExecutedRoutedEventArgs e)
{
...
using (var ffmpeg = new Process())
{
...
ffmpeg.EnableRaisingEvents = true;
ffmpeg.ErrorDataReceived += FfmpegProcess_ErrorDataReceived;
// I realize it is weird I am working with ErrorDataReceived instead
// of OutputDataReceived event, but that's how ffmpeg.exe rolls.
ffmpeg.Start();
ffmpeg.BeginErrorReadLine();
}
}
private void FfmpegProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
var processOutput = e.Data;
var percentComplete = ParsePercentComplete(processOutput);
//TODO Pass percentComplete to Conversion.PercentComplete!?
}
Class:
public class Conversion : INotifyPropertyChanged
{
private double _percentComplete;
public double PercentComplete
{
get { return _percentComplete; }
set
{
_percentComplete = value;
RaisePropertyChange("PercentComplete");
}
}
public void RaisePropertyChange(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Ok, I solved it. The key to the solution was the process.Id which provides the reference to the process specific to the ObservableCollection item.
Specifically, I expanded the Conversion with Process Process property to store the information of that particular process, and then I can find the item in the collection and update its properties from process output in process' event handler.
Here is the updated code:
Code-behind:
private ObservableCollection<Conversion> _queue;
public ObservableCollection<Conversion> Queue
{
get { return _queue; }
set
{
_queue = value;
RaisePropertyChange("Queue");
}
}
private Conversion _selectedItem;
public Conversion SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
RaisePropertyChange("SelectedItem");
}
}
private void RunConversion_Executed(object sender, ExecutedRoutedEventArgs e)
{
...
var ffmpeg = new Process();
ffmpeg.EnableRaisingEvents = true;
ffmpeg.ErrorDataReceived += FfmpegProcess_ErrorDataReceived;
ffmpeg.Start();
conversion.Process = ffmpeg; // This is new
ffmpeg.BeginErrorReadLine();
}
private void FfmpegProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
var processOutput = e.Data;
var percentComplete = ParsePercentComplete(processOutput);
var processId = (sender as Process).Id; // These three lines are new
var conversion = Queue.Where(c => c.Process.Id == processId).FirstOrDefault();
conversion.PercentComplete = percentComplete; // WTF!!!!
}
Class
public class Conversion : INotifyPropertyChanged
{
private double _percentComplete;
public double PercentComplete
{
get { return _percentComplete; }
set
{
_percentComplete = value;
RaisePropertyChange("PercentComplete");
}
}
// New property
private Process _process;
public Process Process
{
get { return _process; }
set
{
_process= value;
RaisePropertyChange("Process");
}
}
public void RaisePropertyChange(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
CheckList Box from WPFToolKit. Below is XAML code (MainWindow.xaml)
<xctk:CheckListBox x:Name="SiteCheckList" Margin="0,0,512,0" Height="100" Width="150"
ItemsSource="{Binding SiteList}"
DisplayMemberPath="SiteName"
CheckedMemberPath="IsChecked">
</xctk:CheckListBox>
Below Properties added in Model Class. I would like to get Checked Items from CheckListBox to my string List (Model.cs).
This string List I will be using for further in project logic.
private string _SiteName;
public string SiteName
{
get { return _SiteName; }
set { _SiteName = value; }
}
private List<string> _SelectedSiteList;
public List<string> SelectedSiteList
{
get { return _SelectedSiteList; }
set
{
_SelectedSiteList = value;
}
}
View Model (ViewModel.cs)
class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<Model> _SiteList;
public ObservableCollection<DataModel> SiteList
{
get { return _SiteList; }
set { _SiteList = value; }
}
public ViewModel()
{
SiteList = new ObservableCollection<Model>();
PoppulateSiteNames();
}
private void PoppulateSiteNames()
{
Dictionary<string, string> keyValuePairs = new Dictionary<string, string>();
keyValuePairs = Files.ReadIni_KeyValue("SiteSection");
foreach (string Key in keyValuePairs.Keys)
{
keyValuePairs.TryGetValue(Key, out string LogTable);
SiteList.Add(new Model() { SiteName = LogTable });
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string PropertyName)
{
if (PropertyChanged !=null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
Here I would like to get list of Checked / Selected Items from UI. If I don't want to write any code in MainWindow.cs i.e. CheckedChanged event then How I can do it by using Binding method ?
Updated Model Class with IsChecked Boolean Property.
private bool _IsChecked;
public bool IsChecked
{
get { return _IsChecked; }
set { _IsChecked = value; }
}
Then ViewModel Updated with Below Function to Populate SiteList
private void PoppulateSiteNames()
{
Dictionary<string, string> keyValuePairs = Files.ReadIni_KeyValue(Vars.MSSQL_Section);
string [] Sites = keyValuePairs.Values.ToArray();
for (int i = 0; i < Sites.Length; i++)
{
SiteList.Add(new HistoricalDataModel { SiteName = Sites[i] });
}
}
Now, Finally I got CheckedItems in below Variable using LINQ
var checkedSites = from site in SiteList
where (site.IsChecked == true)
select new { site.SiteName };
Thank you all of you for responding my question and triggering me to think more.
i am developing a media player using MVVM Light and windows 8.1 store app.
I would like to synchronize my mediaelement with a slider so i can go to any part of my media just by interacting with the slider.
Here is how my slider is declared in my view :
<Slider Width="589" Height="102" Value="{Binding Position, Mode=TwoWay}" Minimum="0" Maximum="{Binding PosMax, Mode=TwoWay}"/>
Here is how i manage my viewModel :
public class PlayerViewModel : ViewModelBase
{
private readonly MediaElement _video;
private readonly string _filename;
private double _volume;
private double _position;
private double _posmax;
public event PropertyChangedEventHandler PropertyChanged;
private DispatcherTimer _timer = null;
public PlayerViewModel()
{
_filename = "animal.mp4";
_video = new MediaElement { AutoPlay = true };
//don't load the stream until the control is ready
_video.Loaded += VideoLoaded;
_volume = 1;
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
public double Volume
{
set
{
_volume = value;
OnPropertyChanged("Volume");
_video.Volume = _volume;
}
get
{
return _volume;
}
}
public double Position
{
set
{
_position = (int) value;
setMediaPostion(_position);
OnPropertyChanged("Position");
}
get
{
return _position;
}
}
public double PosMax
{
set
{
_posmax = value;
OnPropertyChanged("PosMax");
}
get
{
return _posmax;
}
}
private void setMediaPostion(double _position)
{
TimeSpan ts = new TimeSpan(0, 0, 0, 0, (int)_position);
_video.Position = ts;
}
public MediaElement Video
{
get { return _video; }
}
private async void VideoLoaded(object sender, RoutedEventArgs e)
{
var file = await KnownFolders.VideosLibrary.GetFileAsync(_filename);
var stream = await file.OpenAsync(FileAccessMode.Read);
_video.SetSource(stream, file.FileType);
_posmax = (double)_video.NaturalDuration.TimeSpan.TotalMilliseconds;
}
}
If anyone has an idea, i'll be very grateful.
if your mediaElement is in xaml :
<MediaElement Name="media" Source="video.wmv"/>
<Slider Minimum="0" Maximum="1" Width="589" Height="102" Value="{Binding Path=Position,ElementName=media}" />
I have a custom UserControl subclassing from RichTextBox. This class has a dependency property, Equation, that is bound two-way.
When the user drops an item onto the control I change Equation. This properly propagates the change to the other end of the binding, which triggers a property changed notification, but the UI is not changing. If I change the binding to a different object and back it then displays the updated Equation.
How can I force the refresh without changing the binding? Right now I'm setting Equation=null and then back which works, but that seems hackish. There must be something more elegant.
Here are relevant portions of the control. What I would like to happen is for the OnEquationChanged callback to be called after I change Equation (Equation.Components.Add(txt)).
public class EquationTextBox : RichTextBox
{
protected override void OnDrop(DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.StringFormat))
{
string str = (string)e.Data.GetData(DataFormats.StringFormat);
EquationText txt = new EquationText(str);
//// Preferred /////
Equation.Components.Add(txt);
//// HACK /////
Equation eqn = this.Equation;
eqn.Components.Add(txt);
this.Equation = null;
this.Equation = eqn;
///////////////
Console.WriteLine("Dropping " + str);
}
}
public Equation Equation
{
get { return (Equation)GetValue(EquationProperty); }
set { SetValue(EquationProperty, value); }
}
private static void onEquationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
string prop = e.Property.ToString();
EquationTextBox txtBox = d as EquationTextBox;
if(txtBox == null || txtBox.Equation == null)
return;
FlowDocument doc = txtBox.Document;
doc.Blocks.Clear();
doc.Blocks.Add(new Paragraph(new Run(txtBox.Equation.ToString())));
}
public static readonly DependencyProperty EquationProperty =
DependencyProperty.Register("Equation",
typeof(Equation),
typeof(EquationTextBox),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(onEquationChanged)));
private bool mIsTextChanged;
}
}
Here is the property on the other end of the two-way binding. The equation_PropertyChanged event is getting called in the above code as a result of Equation.Components.Add(txt);
public Equation Equation
{
get{ return mEquation; }
set { mEquation = value; NotifyPropertyChanged(); }
}
private void equation_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyPropertyChanged("Equation");
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
Edit --------------------------
Per the comments, I tried using a dispatcher like this (note that this is my first attempt at using a dispatcher)
string str = (string)e.Data.GetData(DataFormats.StringFormat);
EquationText txt = new EquationText(str);
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{
Equation.Components.Add(txt);
NotifyPropertyChanged("Equation");
}));
but still no UI update.
Edit 2 --------------------------
The 2-way binding is done in XAML
<l:EquationTextBox x:Name="ui_txtVariableEquation" Grid.Row="0" Grid.Column="2"
Grid.RowSpan="3" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
AllowDrop="True"
Equation="{Binding SelectedVariableVM.Variable.Equation, Mode=TwoWay}">
</l:EquationTextBox>
Info relevant to the Components object (with in the Equation class)
public class Equation : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Equation()
{
mComponents = new ObservableCollection<EquationComponent>();
mComponents.CollectionChanged += new NotifyCollectionChangedEventHandler(components_CollectionChanged);
}
public Equation(string eqn) : this()
{
mComponents.Add(new EquationText(eqn));
}
public ObservableCollection<EquationComponent> Components
{
get{ return mComponents; }
set{ mComponents = value; NotifyPropertyChanged();}
}
public override string ToString()
{
string str = "";
for(int i=0; i<mComponents.Count; i++)
str += mComponents[i].ToString();
return str;
}
private void components_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("Components");
}
private ObservableCollection<EquationComponent> mComponents;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Variable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Variable(string name = "var", VariableType type = VariableType.UnknownType) :
this(name, "", 0, type)
{
}
and ...
public class Variable : INotifyPropertyChanged
{
public Variable(string name, string unit, object val, VariableType type)
{
mEquation = new Equation(name + " = " + val.ToString() +
mEquation.PropertyChanged += new PropertyChangedEventHandler(equation_PropertyChanged);
}
...
public Equation Equation
{
get{ return mEquation; }
set { mEquation = value; NotifyPropertyChanged(); }
}
private void equation_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyPropertyChanged("Equation");
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private Equation mEquation;
...
}
Variable.equation_PropertyChanged is called when the event is raised inside of the Equation class
I think the problem is that the value produced by the binding is not actually changing (it's still the same Equation object). If the DP value doesn't change, then your DP change handler will not be called.
Perhaps, in your DP change handler, you should subscribe to the new equation's PropertyChanged event and then rebuild your document when an underlying property changes:
private static void onEquationChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var txtBox = d as EquationTextBox;
if (txtBox == null)
return;
var oldEquation = e.OldValue as Equation;
if (oldEquation != null)
oldEquation.PropertyChanged -= txtBox.OnEquationPropertyChanged;
var newEquation = e.NewValue as Equation;
if (newEquation != null)
newEquation.PropertyChanged += txtBox.OnEquationPropertyChanged;
txtBox.RebuildDocument();
}
private void OnEquationPropertyChanged(object sender, EventArgs e)
{
RebuildDocument();
}
private void RebuildDocument()
{
FlowDocument doc = this.Document;
doc.Blocks.Clear();
var equation = this.Equation;
if (equation != null)
doc.Blocks.Add(new Paragraph(new Run(equation.ToString())));
}
I have a class AdvanceBasicEffect, it has a property SpecularColor which is object of class AdvanceVector3 class, so when i bind specularColor.X property, property change event fires but only in AdvanceVector3 class not in AdvanceBasicEffect.
See the code you will figure out :
public partial class Lights : UserControl
{
public Lights()
{
InitializeComponent();
this.DataContext = this;
basicEffect = new AdvanceBasicEffect();
}
public AdvanceBasicEffect basicEffect { get; set; }
}
public class AdvanceBasicEffect : INotifyPropertyChanged
{
public AdvanceBasicEffect()
{
SpecularColor = new AdvanceVector3();
basicEffect = ((bathroom)CurrentWindowHandle.currentGame.Components.First()).basicEffect;
}
BasicEffect basicEffect;
AdvanceVector3 _SpecularColor;
public AdvanceVector3 SpecularColor
{
get
{
return _SpecularColor;
}
set
{
//Line 1 : event not occuring
_SpecularColor = value;
if(basicEffect!=null)
basicEffect.DirectionalLight0.Direction = new Vector3(_SpecularColor.X, _SpecularColor.Y, _SpecularColor.Z);
valueChanged("SpecularColor");
}
}
private void valueChanged(string p)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(p));
CurrentWindowHandle.currentGame.models[0].SetMeshEffect("Archinteriors7_10_80", basicEffect, false);
CurrentWindowHandle.currentGame.models[0].SetMeshEffect("Archinteriors7_10_111", basicEffect, false);
CurrentWindowHandle.currentGame.models[0].SetMeshEffect("Archinteriors7_10_112", basicEffect, false);
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class AdvanceVector3 : INotifyPropertyChanged
{
float _X;
public float X
{
get
{
return _X;
}
set
{
_X = value;
valueChanged("X");
}
}
float _Y;
public float Y
{
get
{
return _Y;
}
set
{
_Y = value;
valueChanged("Y");
}
}
float _Z;
public float Z
{
get
{
return _Z;
}
set
{
_Z = value;
valueChanged("Z");
}
}
private void valueChanged(string p)
{
//line 2 :Event Occuring
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(p));
//CurrentWindowHandle.currentGame.models[0].SetMeshEffect("Archinteriors7_10_80", basicEffect, false);
//CurrentWindowHandle.currentGame.models[0].SetMeshEffect("Archinteriors7_10_111", basicEffect, false);
//CurrentWindowHandle.currentGame.models[0].SetMeshEffect("Archinteriors7_10_112", basicEffect, false);
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
At comment "Line 1" event is occurring whereas on "Line 2" event occur, so my question is when the sub property changes then why is the parent property change not occuring. I can not define X,Y,Z in same class as i have many properties which are going to need AdvanceVector3 class
Binding code is as follows :
<Slider ToolTip="SpecularColorX" Minimum="-1" Maximum="1" Value="{Binding basicEffect.SpecularColor.X}" />
<Slider ToolTip="SpecularColorY" Minimum="-1" Maximum="1" Value="{Binding basicEffect.SpecularColor.Y}" />
<Slider ToolTip="SpecularColorZ" Minimum="-1" Maximum="1" Value="{Binding basicEffect.SpecularColor.Z}" />
If I follow your question correctly, I think your misunderstanding what INPC means and when it triggers.
If you say have
Class ParentClass : INotifyPropertyChanged {
public ChildClass SomeObject {
get { ... }
set { ... valueChanged("SomeObject"); }
}
...
}
and
Class ChildClass : INotifyPropertyChanged {
public string SomeString {
get { ... }
set { ... valueChanged("SomeString"); }
}
...
}
Now if you either from xaml or code-behind change SomeString in an object of ParentClass
var parentObject = new ParentClass {SomeObject = new ChildClass {SomeString = "Hi"}};
// will trigger property changed in ChildClass for SomeString property
// however it will not trigger a PropertyChanged in ParentClass for SomeObject
parentObject.SomeObject.SomeString = "New String"
// will trigger property changed in ParentClass for SomeObject property
parentObject.SomeObject = new ChildClass();
Your xaml bindings work fine since you bind directly to the properties and as and when they change your View is updated accordingly.
If you are wanting to "observe" any changes to SomeString from inside the ParentClass, then you need to subscribe to the PropertyChanged event of ChildClass.SomeString
so say ParentClass can be updated to
public ChildClass SomeObject {
get { ... }
set {
if (value == _someObject)
return;
_someObject.PropertyChanged -= ChildObjectValueChanged;
_someObject = value;
_someObject.PropertyChanged += ChildObjectValueChanged;
valueChanged("SomeObject"); }
}
private void ChildObjectValueChanged(object sender, PropertyChangedEventArgs args) {
if (args.PropertyName == "SomeString")
// Child Object property "SomeString" has changed. Do what you need here
}
Your setter never gets called. Wpf itself calls PropertyChagned on INotifyPropertyChanged imlplementations. What you should do is to subsribe to changed event yourself in your AdvancedBasicEffect class constructor instead of setting mesh effect in valueChanged method:
public AdvanceBasicEffect()
{
SpecularColor = new AdvanceVector3();
basicEffect = ((bathroom)CurrentWindowHandle.currentGame.Components.First()).basicEffect;
SpecularColor.PropertyChanged += (o,e) =>
{
CurrentWindowHandle.currentGame.models[0].SetMeshEffect("Archinteriors7_10_80", basicEffect, false);
CurrentWindowHandle.currentGame.models[0].SetMeshEffect("Archinteriors7_10_111", basicEffect, false);
CurrentWindowHandle.currentGame.models[0].SetMeshEffect("Archinteriors7_10_112", basicEffect, false);
}
}