View Model problem??

Leon ng 0 Reputation points
2023-11-06T03:45:02.7033333+00:00

Hi there,

I'm tring to develop a UI to show 3 real time chart. I try to use a MainWindow, 3 ViewModel, and 3 View. In MainWindow, it will show the View and collect the data, then I need to call a worker to update the view.

ViewModel:User's image

MainWindow:

User's image

How should I improve this program??

Best Regards,
Leon Ng

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,795 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
11,111 questions
{count} votes

2 answers

Sort by: Most helpful
  1. Peter Fleischer (former MVP) 19,326 Reputation points
    2023-11-08T06:27:40.09+00:00

    Hi Leon,
    I change a little bit your code for demo purposes:

    XAML MainWindow:

    <Window x:Class="WpfApp1.Window118"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp118"
            xmlns:v="clr-namespace:WpfApp1"
            mc:Ignorable="d" Top="100" Left="100"
            Title="Leon_ng_231106" Height="400" Width="400">
      <Window.Resources>
        <v:Window118Graph1 x:Key="graph1"/>
        <v:Window118Graph1 x:Key="graph2"/>
        <local:MainVM x:Key="vm"/>
      </Window.Resources>
      <Grid DataContext="{StaticResource vm}">
        <Grid.RowDefinitions>
          <RowDefinition Height="*"/>
          <RowDefinition Height="40"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
          <ColumnDefinition/>
          <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <ContentControl x:Name="Graph1" Grid.Row="0" Grid.Column="0" local:MainVM.AttProp="True" Content="{StaticResource graph1}"/>
        <ContentControl x:Name="Graph2" Grid.Row="0" Grid.Column="1" local:MainVM.AttProp="True" Content="{StaticResource graph2}"/>
        <Button Grid.Row="1" Grid.Column="0" Content="Start Graph 1" Command="{Binding}" CommandParameter="Graph1" Margin="5"/>
        <Button Grid.Row="1" Grid.Column="1" Content="Start Graph 2" Command="{Binding}" CommandParameter="Graph2" Margin="5"/>
      </Grid>
    </Window>
    

    XAML UserControl:

    <UserControl x:Class="WpfApp1.Window118Graph1"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 xmlns:local="clr-namespace:WpfApp118"
                 xmlns:oxyplot="clr-namespace:WpfApp118"
                 mc:Ignorable="d" 
                 d:DesignHeight="450" d:DesignWidth="800">
      <Border>
        <oxyplot:PlotView Data="{Binding Points}" />
      </Border>
    </UserControl>
    

    And all code:

    using System;
    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using System.Threading;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Shapes;
    
    namespace WpfApp1
    {
    	/// <summary>
    	/// Interaction logic for Window051.xaml
    	/// </summary>
    	public partial class Window118 : Window
    	{
    		public Window118()
    		{
    			InitializeComponent();
    		}
    	}
    }
    
    namespace WpfApp118
    {
    	public class MainVM : INotifyPropertyChanged, ICommand
    	{
    
    		private Graph1VM _vm1 = new Graph1VM();
    		public Graph1VM VM1
    		{
    			get { return _vm1; }
    			set { _vm1 = value; OnPropertyChanged(); }
    		}
    
    		private Graph2VM _vm2 = new Graph2VM();
    		public Graph2VM VM2
    		{
    			get { return _vm2; }
    			set { _vm2 = value; OnPropertyChanged(); }
    		}
    
    		public static readonly DependencyProperty AttPropProperty = DependencyProperty.Register("AttProp",
    			typeof(bool), typeof(ContentControl), new UIPropertyMetadata(false, OnAttProp));
    		public static bool GetAttProp(DependencyObject obj) => (bool)obj.GetValue(AttPropProperty);
    		public static void SetAttProp(DependencyObject obj, bool value) => obj.SetValue(AttPropProperty, value);
    		private static void OnAttProp(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
    		{
    			var cc = depObj as ContentControl;
    			if (cc == null) return;
    			if ((e.NewValue is bool) && (bool)(e.NewValue))
    			{
    				MainVM oldDC = (MainVM)cc.DataContext;
    				switch (cc.Name)
    				{
    					case "Graph1":
    						cc.DataContext = oldDC.VM1;
    						break;
    					case "Graph2":
    						cc.DataContext = oldDC.VM2;
    						break;
    					default:
    						break;
    				}
    			}
    		}
    		public void Execute(object parameter)
    		{
    
    			switch (parameter.ToString())
    			{
    				case "Graph1":
    					if (!VM1.worker_updategraph.IsBusy) VM1.worker_updategraph.RunWorkerAsync();
    					break;
    				case "Graph2":
    					if (!VM2.worker_updategraph.IsBusy) VM2.worker_updategraph.RunWorkerAsync();
    					break;
    				default:
    					break;
    			}
    		}
    		public bool CanExecute(object parameter) => true;
    
    		SynchronizationContext sc = SynchronizationContext.Current;
    		public event PropertyChangedEventHandler PropertyChanged;
    		public event EventHandler CanExecuteChanged;
    		private void OnPropertyChanged([CallerMemberName] string propName = "") =>
    			sc.Post(new SendOrPostCallback((p) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName))), null);
    	}
    
    	public class Graph1VM : INotifyPropertyChanged
    	{
    		public Graph1VM()
    		{
    			worker_updategraph = new BackgroundWorker();
    			worker_updategraph.DoWork += worker_updategraph_DoWork;
    		}
    
    		Random random = new Random();
    		int time = 0;
    		internal BackgroundWorker worker_updategraph;
    
    		public void worker_updategraph_DoWork(object sender, DoWorkEventArgs e)
    		{
    			while (true)
    			{
    				readData();
    				time++;
    				System.Threading.Thread.Sleep(50);
    			}
    		}
    
    		public double[] Points { get; set; } = new double[100];
    		Random rnd = new Random(100);
    		public void readData()
    		{
    			double[] db = new double[100];
    			for (int i = 0; i < Points.GetUpperBound(0); i++) db[i] = Points[i + 1];
    			db[99] = rnd.Next(20, 200);
    			Points = db;
    			OnPropertyChanged(nameof(Points));
    			if (worker_updategraph.IsBusy == false) worker_updategraph.RunWorkerAsync();
    		}
    
    		SynchronizationContext sc = SynchronizationContext.Current;
    		public event PropertyChangedEventHandler PropertyChanged;
    		public event EventHandler CanExecuteChanged;
    		private void OnPropertyChanged([CallerMemberName] string propName = "") =>
    			sc.Post(new SendOrPostCallback((p) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName))), null);
    	}
    
    	public class Graph2VM : INotifyPropertyChanged
    	{
    		public Graph2VM()
    		{
    			worker_updategraph = new BackgroundWorker();
    			worker_updategraph.DoWork += worker_updategraph_DoWork;
    		}
    
    		Random random = new Random(200);
    		int time = 0;
    		internal BackgroundWorker worker_updategraph;
    
    		public void worker_updategraph_DoWork(object sender, DoWorkEventArgs e)
    		{
    			while (true)
    			{
    				readData();
    				time++;
    				System.Threading.Thread.Sleep(50);
    			}
    		}
    
    		public double[] Points { get; set; } = new double[100];
    		Random rnd = new Random();
    		public void readData()
    		{
    			double[] db = new double[100];
    			for (int i = 0; i < Points.GetUpperBound(0); i++) db[i] = Points[i + 1];
    			db[99] = rnd.Next(20, 200);
    			Points = db;
    			OnPropertyChanged(nameof(Points));
    			if (worker_updategraph.IsBusy == false) worker_updategraph.RunWorkerAsync();
    		}
    
    		SynchronizationContext sc = SynchronizationContext.Current;
    		public event PropertyChangedEventHandler PropertyChanged;
    		public event EventHandler CanExecuteChanged;
    		private void OnPropertyChanged([CallerMemberName] string propName = "") =>
    			sc.Post(new SendOrPostCallback((p) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName))), null);
    	}
    
    	class PlotView : FrameworkElement
    	{
    		VisualCollection children;
    		Canvas plotArea;
    		Polyline poly;
    		Line xAxis, yAxis;
    		Point Origin = new Point(10, 10);
    		double[] Points { get; set; } = new double[0];
    
    		public PlotView()
    		{
    			children = new VisualCollection(this);
    			initAxis();
    			initPlotArea();
    		}
    
    		public int XPoints { get; set; } = 100;
    		public int YPoints { get; set; } = 100;
    		public Brush LineColor { get; set; } = Brushes.Red;
    
    		public static readonly DependencyProperty DataProperty
    		= DependencyProperty.RegisterAttached("Data", typeof(double[]), typeof(PlotView),
    			new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsArrange,
    				new PropertyChangedCallback(DataChanged)));
    		public static double[] GetData(DependencyObject obj) => (double[])obj.GetValue(DataProperty);
    		public static void SetData(DependencyObject obj, double[] data) => obj.SetValue(DataProperty, data);
    
    		private static void DataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    		{
    			var plot = d as PlotView;
    			if (plot == null) throw new InvalidOperationException("Error using Data property.");
    			var data = e.NewValue as double[];
    			plot.Points = data;
    		}
    
    		void initAxis()
    		{
    			xAxis = new Line() { Stroke = Brushes.LightBlue, StrokeThickness = 2 };
    			yAxis = new Line() { Stroke = Brushes.LightBlue, StrokeThickness = 2 };
    			children.Add(xAxis);
    			children.Add(yAxis);
    		}
    
    		void initPlotArea()
    		{
    			plotArea = new Canvas()
    			{
    				LayoutTransform = new ScaleTransform() { ScaleY = -1 },
    				RenderTransform = new TransformGroup() { Children = { new ScaleTransform(), new TranslateTransform() } }
    			};
    			children.Add(plotArea);
    			poly = new Polyline() { Stroke = LineColor, StrokeThickness = 1 };
    			plotArea.Children.Add(poly);
    		}
    
    		void rearrangeAxis(Size size)
    		{
    			xAxis.X1 = 0;
    			xAxis.X2 = size.Width;
    			xAxis.Y1 = xAxis.Y2 = size.Height - Origin.Y;
    			yAxis.X1 = yAxis.X2 = Origin.X;
    			yAxis.Y1 = 0;
    			yAxis.Y2 = size.Height;
    			xAxis.Measure(size);
    			xAxis.Arrange(new Rect(xAxis.DesiredSize));
    			yAxis.Measure(size);
    			yAxis.Arrange(new Rect(yAxis.DesiredSize));
    		}
    
    		void rearrangePlotArea(Size size)
    		{
    			plotArea.Width = size.Width - Origin.X;
    			plotArea.Height = size.Height - Origin.Y;
    			var translate = (TranslateTransform)((TransformGroup)plotArea.RenderTransform).Children[1];
    			translate.X = Origin.X;
    			translate.Y = 0;
    			plotArea.Measure(size);
    			plotArea.Arrange(new Rect(plotArea.DesiredSize));
    			var points = new PointCollection();
    			double xStep = plotArea.Width / XPoints;
    			double yFactor = plotArea.Height / YPoints;
    			for (int i = 0; i < YPoints && i <= Points.GetUpperBound(0); i++)
    				points.Add(new Point(i * xStep, Points[i]));
    			poly.Points = points;
    		}
    
    		protected override Size ArrangeOverride(Size finalSize)
    		{
    			rearrangeAxis(finalSize);
    			rearrangePlotArea(finalSize);
    			return finalSize;
    		}
    
    		protected override Visual GetVisualChild(int index) => children[index];
    		protected override int VisualChildrenCount => children.Count;
    	}
    }
    
    

    Result:

    x


  2. Peter Fleischer (former MVP) 19,326 Reputation points
    2023-11-09T05:55:11.0633333+00:00

    Hi Leon,
    using readData in Main you must select Graph1VM or Graph2VM for adding Points.

    Try following demo:

    XAML MainWindow:

    <Window x:Class="WpfApp1.Window119"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp119"
            xmlns:v="clr-namespace:WpfApp1"
            mc:Ignorable="d" Top="100" Left="100"
            Title="Leon_ng_231106" Height="400" Width="400">
      <Window.Resources>
        <v:Window118Graph1 x:Key="graph1"/>
        <v:Window118Graph1 x:Key="graph2"/>
        <local:MainVM x:Key="vm"/>
      </Window.Resources>
      <Grid DataContext="{StaticResource vm}">
        <Grid.RowDefinitions>
          <RowDefinition Height="*"/>
          <RowDefinition Height="40"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
          <ColumnDefinition/>
          <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <ContentControl x:Name="Graph1" Grid.Row="0" Grid.Column="0" local:MainVM.AttProp="True" Content="{StaticResource graph1}"/>
        <ContentControl x:Name="Graph2" Grid.Row="0" Grid.Column="1" local:MainVM.AttProp="True" Content="{StaticResource graph2}"/>
        <Button Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Content="Start" Command="{Binding}" Margin="5"/>
      </Grid>
    </Window>
    

    XAML UserControl:

    <UserControl x:Class="WpfApp1.Window119Graph1"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 xmlns:local="clr-namespace:WpfApp119"
                 xmlns:oxyplot="clr-namespace:WpfApp119"
                 mc:Ignorable="d" 
                 d:DesignHeight="450" d:DesignWidth="800">
      <Border>
        <oxyplot:PlotView Data="{Binding Points}" />
      </Border>
    </UserControl>
    

    Code:

    using System;
    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using System.Threading;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Shapes;
    
    namespace WpfApp1
    {
    	/// <summary>
    	/// Interaction logic for Window119.xaml
    	/// </summary>
    	public partial class Window119 : Window
    	{
    		public Window119()
    		{
    			InitializeComponent();
    		}
    	}
    }
    
    namespace WpfApp119
    {
    	public class MainVM : INotifyPropertyChanged, ICommand
    	{
    		public MainVM()
    		{
    			worker_updategraph = new BackgroundWorker();
    			worker_updategraph.DoWork += worker_updategraph_DoWork;
    		}
    
    		private Graph1VM _vm1 = new Graph1VM();
    		public Graph1VM VM1
    		{
    			get { return _vm1; }
    			set { _vm1 = value; OnPropertyChanged(); }
    		}
    
    		private Graph2VM _vm2 = new Graph2VM();
    		public Graph2VM VM2
    		{
    			get { return _vm2; }
    			set { _vm2 = value; OnPropertyChanged(); }
    		}
    
    		public static readonly DependencyProperty AttPropProperty = DependencyProperty.Register("AttProp",
    			typeof(bool), typeof(ContentControl), new UIPropertyMetadata(false, OnAttProp));
    		public static bool GetAttProp(DependencyObject obj) => (bool)obj.GetValue(AttPropProperty);
    		public static void SetAttProp(DependencyObject obj, bool value) => obj.SetValue(AttPropProperty, value);
    		private static void OnAttProp(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
    		{
    			var cc = depObj as ContentControl;
    			if (cc == null) return;
    			if ((e.NewValue is bool) && (bool)(e.NewValue))
    			{
    				MainVM oldDC = (MainVM)cc.DataContext;
    				switch (cc.Name)
    				{
    					case "Graph1":
    						cc.DataContext = oldDC.VM1;
    						break;
    					case "Graph2":
    						cc.DataContext = oldDC.VM2;
    						break;
    					default:
    						break;
    				}
    			}
    		}
    		public void Execute(object parameter)
    		{
    			if (!worker_updategraph.IsBusy) worker_updategraph.RunWorkerAsync();
    		}
    		public bool CanExecute(object parameter) => true;
    
    		Random rnd = new Random();
    		int time = 0;
    		internal BackgroundWorker worker_updategraph;
    
    		public void worker_updategraph_DoWork(object sender, DoWorkEventArgs e)
    		{
    			while (true)
    			{
    				readData();
    				time++;
    				System.Threading.Thread.Sleep(50);
    			}
    		}
    
    		public void readData()
    		{
    			GraphBasic vm;
    			if (rnd.NextDouble() > .5) vm = VM1; else vm = VM2;
    			double[] db = new double[100];
    			for (int i = 0; i < vm.Points.GetUpperBound(0); i++) db[i] = vm.Points[i + 1];
    			db[99] = rnd.Next(20, 200);
    			vm.Points = db;
    			OnPropertyChanged(nameof(vm.Points));
    			if (worker_updategraph.IsBusy == false) worker_updategraph.RunWorkerAsync();
    		}
    
    		SynchronizationContext sc = SynchronizationContext.Current;
    		public event PropertyChangedEventHandler PropertyChanged;
    		public event EventHandler CanExecuteChanged;
    		private void OnPropertyChanged([CallerMemberName] string propName = "") =>
    			sc.Post(new SendOrPostCallback((p) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName))), null);
    	}
    
    	public abstract class GraphBasic : INotifyPropertyChanged
    	{
    		public abstract double[] Points { get; set; }
    
    		SynchronizationContext sc = SynchronizationContext.Current;
    		public event PropertyChangedEventHandler PropertyChanged;
    		public event EventHandler CanExecuteChanged;
    		internal void OnPropertyChanged([CallerMemberName] string propName = "") =>
    			sc.Post(new SendOrPostCallback((p) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName))), null);
    	}
    
    	public class Graph1VM : GraphBasic
    	{
    		private double[] _points = new double[100];
    		public override double[] Points
    		{
    			get => this._points;
    			set { _points = value; OnPropertyChanged(); }
    		}
    	}
    
    	public class Graph2VM : GraphBasic
    	{
    		private double[] _points = new double[100];
    		public override double[] Points
    		{
    			get => this._points;
    			set { _points = value; OnPropertyChanged(); }
    		}
    	}
    
    	class PlotView : FrameworkElement
    	{
    		VisualCollection children;
    		Canvas plotArea;
    		Polyline poly;
    		Line xAxis, yAxis;
    		Point Origin = new Point(10, 10);
    		double[] Points { get; set; } = new double[0];
    
    		public PlotView()
    		{
    			children = new VisualCollection(this);
    			initAxis();
    			initPlotArea();
    		}
    
    		public int XPoints { get; set; } = 100;
    		public int YPoints { get; set; } = 100;
    		public Brush LineColor { get; set; } = Brushes.Red;
    
    		public static readonly DependencyProperty DataProperty
    		= DependencyProperty.RegisterAttached("Data", typeof(double[]), typeof(PlotView),
    			new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsArrange,
    				new PropertyChangedCallback(DataChanged)));
    		public static double[] GetData(DependencyObject obj) => (double[])obj.GetValue(DataProperty);
    		public static void SetData(DependencyObject obj, double[] data) => obj.SetValue(DataProperty, data);
    
    		private static void DataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    		{
    			var plot = d as PlotView;
    			if (plot == null) throw new InvalidOperationException("Error using Data property.");
    			var data = e.NewValue as double[];
    			plot.Points = data;
    		}
    
    		void initAxis()
    		{
    			xAxis = new Line() { Stroke = Brushes.LightBlue, StrokeThickness = 2 };
    			yAxis = new Line() { Stroke = Brushes.LightBlue, StrokeThickness = 2 };
    			children.Add(xAxis);
    			children.Add(yAxis);
    		}
    
    		void initPlotArea()
    		{
    			plotArea = new Canvas()
    			{
    				LayoutTransform = new ScaleTransform() { ScaleY = -1 },
    				RenderTransform = new TransformGroup() { Children = { new ScaleTransform(), new TranslateTransform() } }
    			};
    			children.Add(plotArea);
    			poly = new Polyline() { Stroke = LineColor, StrokeThickness = 1 };
    			plotArea.Children.Add(poly);
    		}
    
    		void rearrangeAxis(Size size)
    		{
    			xAxis.X1 = 0;
    			xAxis.X2 = size.Width;
    			xAxis.Y1 = xAxis.Y2 = size.Height - Origin.Y;
    			yAxis.X1 = yAxis.X2 = Origin.X;
    			yAxis.Y1 = 0;
    			yAxis.Y2 = size.Height;
    			xAxis.Measure(size);
    			xAxis.Arrange(new Rect(xAxis.DesiredSize));
    			yAxis.Measure(size);
    			yAxis.Arrange(new Rect(yAxis.DesiredSize));
    		}
    
    		void rearrangePlotArea(Size size)
    		{
    			plotArea.Width = size.Width - Origin.X;
    			plotArea.Height = size.Height - Origin.Y;
    			var translate = (TranslateTransform)((TransformGroup)plotArea.RenderTransform).Children[1];
    			translate.X = Origin.X;
    			translate.Y = 0;
    			plotArea.Measure(size);
    			plotArea.Arrange(new Rect(plotArea.DesiredSize));
    			var points = new PointCollection();
    			double xStep = plotArea.Width / XPoints;
    			double yFactor = plotArea.Height / YPoints;
    			for (int i = 0; i < YPoints && i <= Points.GetUpperBound(0); i++)
    				points.Add(new Point(i * xStep, Points[i]));
    			poly.Points = points;
    		}
    
    		protected override Size ArrangeOverride(Size finalSize)
    		{
    			rearrangeAxis(finalSize);
    			rearrangePlotArea(finalSize);
    			return finalSize;
    		}
    
    		protected override Visual GetVisualChild(int index) => children[index];
    		protected override int VisualChildrenCount => children.Count;
    	}
    }
    

    Result:

    x


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.