How do I asycronously update a UI element from a ViewModel [duplicate] - c#

This question already has answers here:
Error: Must create DependencySource on same Thread as the DependencyObject even by using Dispatcher
(2 answers)
Closed 3 years ago.
I am using a label on multiple forms which display the weather data which is called from a WCF service. I wish to have this update every minute to display the updated weather data without interfering with user interaction.
I get the following error:
"Must create DependencySource on same Thread as the DependencyObject. "
I have a View Model for getting the weather data asynchronously which inherits from ViewModelBase to handle property changed events. The properties from the ViewModel are bound to the label
ViewModel for weather
public class WeatherDataVM : ViewModelBase
{
private string _windString;
private SolidColorBrush _windState;
private DispatcherTimer _timer;
public WeatherDataVM()
{
_timer = new DispatcherTimer(DispatcherPriority.Render);
_timer.Interval = TimeSpan.FromSeconds(10);
_timer.Tick += async (sender, args) => {await Task.Run(() => GetWindAsync()); };
//_timer.Tick += _timer_Tick;
_timer.Start();
GetWind();
}
private void GetWind()
{
var weatherFromService = Services.Instance.EmptyStackService.GetWeather();
var windSpeed = Convert.ToDouble(weatherFromService.Windspeed);
var maxGust = Convert.ToDouble(weatherFromService.Max_Gust_In_Last_Min);
var windSpeedMPH = Math.Round(windSpeed * 1.15078, 1);
var maxGustMPH = Math.Round(maxGust * 1.15078, 1);
var windString = $"W/S: {windSpeedMPH}({maxGustMPH})";
var windState = new Color();
if (windSpeed >= 40)
windState = Color.FromRgb(255, 64, 64);
else if (windSpeed >= 24)
windState = Color.FromRgb(255, 212, 128);
else
windState = Color.FromRgb(0, 255, 0);
_windState = new SolidColorBrush(windState);
_windString = windString;
}
private async Task GetWindAsync()
{
var weatherFromService = Services.Instance.EmptyStackService.GetWeather();
var windSpeed = Convert.ToDouble(weatherFromService.Windspeed);
var maxGust = Convert.ToDouble(weatherFromService.Max_Gust_In_Last_Min);
var windSpeedMPH = Math.Round(windSpeed * 1.15078, 1);
var maxGustMPH = Math.Round(maxGust * 1.15078, 1);
var windString = $"W/S: {windSpeedMPH}({maxGustMPH})";
var windState = new Color();
if (windSpeed >= 40)
windState = Color.FromRgb(255, 64, 64);
else if (windSpeed >= 24)
windState = Color.FromRgb(255, 212, 128);
else
windState = Color.FromRgb(0, 255, 0);
WindState = new SolidColorBrush(windState);
WindString = windString;
}
public string WindString
{
get { return _windString; }
set
{
if (_windString == value)
return;
_windString = value;
OnPropertyChanged("WindString");
}
}
public SolidColorBrush WindState
{
get { return _windState; }
set
{
if (_windState == value)
return;
_windState = value;
OnPropertyChanged("WindState");
}
}
}
ViewModelBase
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Xaml on view for label
<Label x:Name="lblWeather" Content="{Binding WindString}" Foreground="black" Background="{Binding WindState}" Style="{DynamicResource SmallLabel}" />
code behind view in constructor
lblWeather.DataContext = new WeatherDataVM();
the weather label should change each time the timer ticks. Instead it throws an error.

You can create a brush on a background thread if you freeze it:
var brush = new SolidColorBrush(windState);
brush.Freeze();
WindState = brush;
But it doesn't make much sense to use a DispatcherTimer if you just call Task.Run in the Tick event handler.
Provided that your event handler only creates brushes and don't manipulate any UI elements directly (it certainly shouldn't since it's implemented in a view model), you could use a System.Timer.Timer. Its Elapsed event is queued for execution on a thread pool thread where you can query the service without blocking the UI.

Related

How to await an RoutedEvent`? [duplicate]

This question already has answers here:
Is it possible to await an event instead of another async method?
(11 answers)
Closed 8 months ago.
How to Wait for a RoutedEvent? Since there is no definition for GetAwaiter i can't use the await keyword. So how should i modify the Code so that it waits for completion of the GIF Animation?
The Code who calls the RoutedEvent:
public static async Task<Boolean> Draw(List<Point> points, int counter, Polyline current, Color color, MainWindow main)
{
current.Stroke = new SolidColorBrush(color);
current.Points.Clear();
for (int i = 0; i < points.Count; i++)
{
await Task.Delay(TimeSpan.FromMilliseconds(1));
current.Points.Add(points[i]);
if(i == points.Count - 1)
{
var completed = new XamlAnimatedGif.AnimationCompletedEventArgs(current);
await ShowExplosion(main, current.Points.ElementAt(i));
}
}
points.Clear();
current = null;
return true;
}
an the RoutedEvent:
static RoutedEvent ShowExplosion(MainWindow main, Point coordinate)
{
Image explosion = new Image();
explosion.Width = 40;
explosion.Height = 40;
Canvas.SetLeft(explosion, coordinate.X - 20);
Canvas.SetTop(explosion, coordinate.Y - 20);
//Relative URI
var resourcePath = new Uri("pack://application:,,,/Resources/GIF/Explosion2_trsp.gif");
XamlAnimatedGif.AnimationBehavior.SetSourceUri(explosion, resourcePath);
XamlAnimatedGif.AnimationBehavior.SetRepeatBehavior(explosion, new System.Windows.Media.Animation.RepeatBehavior(1));
XamlAnimatedGif.AnimationBehavior.SetAutoStart(explosion, true);
main.canvas_shots.Children.Add(explosion);
return XamlAnimatedGif.AnimationBehavior.AnimationCompletedEvent;
}
You don't await events. Events are synchronous and a different concept than asynchronous methods. While you await asynchronous methods, you listen to events by registering an event handler i.e. callback with the event source (Events (C# Programming Guide), Routed events overview (WPF .NET)).
Unless you have a reference to the event source, you attach an event handler to an element along the event route using the UIElement.AddHandler method. In your case the parent MainWindow:
main.AddHandler(XamlAnimatedGif.AnimationBehavior.AnimationCompletedEvent, OnAnimationCompleted);
Your fixed code:
public static bool Draw(List<Point> points, int counter, Polyline current, Color color, MainWindow main)
{
current.Stroke = new SolidColorBrush(color);
current.Points.Clear();
for (int i = 0; i < points.Count; i++)
{
current.Points.Add(points[i]);
if (i == points.Count - 1)
{
var completed = new XamlAnimatedGif.AnimationCompletedEventArgs(current);
ShowExplosion(main, current.Points.ElementAt(i));
}
}
points.Clear();
current = null;
return true;
}
static void ShowExplosion(MainWindow main, Point coordinate)
{
Image explosion = new Image();
explosion.Width = 40;
explosion.Height = 40;
Canvas.SetLeft(explosion, coordinate.X - 20);
Canvas.SetTop(explosion, coordinate.Y - 20);
//Relative URI
var resourcePath = new Uri("pack://application:,,,/Resources/GIF/Explosion2_trsp.gif");
XamlAnimatedGif.AnimationBehavior.SetSourceUri(explosion, resourcePath);
XamlAnimatedGif.AnimationBehavior.SetRepeatBehavior(explosion, new System.Windows.Media.Animation.RepeatBehavior(1));
XamlAnimatedGif.AnimationBehavior.SetAutoStart(explosion, true);
main.canvas_shots.Children.Add(explosion);
// Listen to the event by registering a callback (event handler)
main.AddHandler(XamlAnimatedGif.AnimationBehavior.AnimationCompletedEvent, OnAnimationCompleted);
}
private void OnAnimationCompleted(object sender, AnimationCompletedEventArgs e)
{
var main = sender as UIElement;
main.RemoveHandler(XamlAnimatedGif.AnimationBehavior.AnimationCompletedEvent, OnAnimationCompleted);
// TODO::Do something after the animation has completed...
}

Helix Toolkit recently added tube objects in WPF application, are not firing events or reacts on mouse interaction?

I am using WPF application with Helix Toolkit, where I dynamically add multiple 3D objects into the Viewport. The Visuals of the objects are added successfully, but some of the recently added objects (in this case tube objects) are not firing events, neither show their appropriate tooltip message. After some interaction with the GUI of the app and adding new additional 3D objects to the Viewport, the old already added objects are firing events and show their tooltip message...
Also I have noticed that if I add some other new 3D objects and if I rotate the camera with right button pressed of the mouse, they are coming responsive!
So what Is causing this problem? Is it there some rendering or camera problem or something else?
I am using this code about the Viewport with SortingVisual3D where all of the objects are added behind the transparent surface:
<helix:HelixViewport3D Grid.Column="0" Grid.Row="1" Grid.RowSpan="10" Background="GhostWhite" Name="_viewport" ShowFrameRate="True" ShowTriangleCountInfo="True" ShowViewCube="True" IsHitTestVisible="True" CalculateCursorPosition="True" ShowCoordinateSystem="True" >
<helix:DefaultLights/>
<helix:SortingVisual3D x:Name="sortingVisual1" Method="BoundingBoxCorners" IsSorting="True" SortingFrequency="4" >
<helix:MeshGeometryVisual3D x:Name="_cubeObjects">
</helix:MeshGeometryVisual3D>
<helix:MeshGeometryVisual3D x:Name="_tubeObjects">
</helix:MeshGeometryVisual3D>
//Transparent surface
<helix:MeshGeometryVisual3D x:Name="_transparentSurface" >
</helix:MeshGeometryVisual3D>
</helix:SortingVisual3D>
</helix:HelixViewport3D>
</strike>
This is how I dynamically add the objects:
AddObject tubeObject = new AddObject(points3dCollection, "Tube Object", tubeDiameter, 36, objectMaterial);
tubeObject.MouseLeftButtonDown += new MouseButtonEventHandler(tubeObjectClick);
tubeObject.IsHitTestVisible = true;
tubeObject.SetName("Tube Object");
ContainerUIElement3D cui = new ContainerUIElement3D();
cui.Children.Add(tubeObject);
_tubeObjects.Children.Add(cui);
'-------------------This is the class AddObject definition:-------------------------------'
class AddObject : UIElement3D,INotifyCollectionChanged
{
private readonly Timer _timer;
private readonly ToolTip _toolTip;
public AddObject DataContext { get; }
public AddObject(Point3DCollection path, string objectName, double diametar_1, int thetaDiv, Material material)
{
MeshBuilder builder = new MeshBuilder();
List<Point3D> list = new List<Point3D>();
for (int i = 0; i < path.Count; i++)
{
list.Add(path[i]);
}
list = CanonicalSplineHelper.CreateSpline(list, 0.5, null, false, 0.9);
builder.AddTube(list, diametar_1, thetaDiv, false, true, true);
GeometryModel3D model = new GeometryModel3D(builder.ToMesh(), material);
model.SetName(objectName);
Visual3DModel = model;
_toolTip = new ToolTip();
_timer = new Timer { AutoReset = false };
_timer.Elapsed += ShowToolTip;
DataContext = this;
}
public event NotifyCollectionChangedEventHandler CollectionChanged
{
add
{
((INotifyCollectionChanged)DataContext).CollectionChanged += value;
}
remove
{
((INotifyCollectionChanged)DataContext).CollectionChanged -= value;
}
}
public object ToolTipContent { get { return _toolTip.Content; } set { _toolTip.Content = value; } }
private void ShowToolTip(object sender, ElapsedEventArgs e)
{
_timer.Stop();
if (_toolTip != null)
_toolTip.Dispatcher.Invoke(new Action(() => { _toolTip.IsOpen = true; }));
}
protected override void OnMouseEnter(MouseEventArgs e)
{
base.OnMouseEnter(e);
var gm = Visual3DModel as GeometryModel3D;
gm.Material = gm.Material == materialtype ? Materials.Yellow : materialtype;
if (_toolTip != null)
{
_toolTip.IsOpen = true;
_toolTip.Content = gm.GetName().ToString().Trim() + " vein "; }
// _timer.Interval = ToolTipService.GetInitialShowDelay(Application.Current.MainWindow);
_timer.Interval = 50;
_timer.Start();
e.Handled = true;
}
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
var gm = Visual3DModel as GeometryModel3D;
gm.Material = gm.Material == materialtype ? Materials.Yellow : materialtype;
_timer.Stop();
if (_toolTip != null)
{ _toolTip.IsOpen = false;
_toolTip.Content = "";
}
e.Handled = true;
}
}
What should I check at first? Any idea is welcomed ! Please help !
The immediate response problem of the recently inserted objects is solved with this code:
<helix:MeshGeometryVisual3D >
<ContainerUIElement3D x:Name="_cubeObjects">
</ContainerUIElement3D>
</helix:MeshGeometryVisual3D>
<helix:MeshGeometryVisual3D >
<ContainerUIElement3D x:Name="_ballClickPoints">
</ContainerUIElement3D>
</helix:MeshGeometryVisual3D>
<helix:MeshGeometryVisual3D >
<ContainerUIElement3D x:Name="_tubeObjects">
</ContainerUIElement3D>
</helix:MeshGeometryVisual3D>
<helix:MeshGeometryVisual3D x:Name="_transparentSurface">
</helix:MeshGeometryVisual3D>
So the change is in using ContainerUIElement3D into MeshGeometryVisual3D.. The objects are responding immediately on mouse events .

Data Binding Successful but propertychange remains null

I had successfully implemented a databind before but now it has stop working... After putting in some breakpoints I have noticed that the PropertyChange event remains null. I looked up several "solutions" involving using DataContext (not sure where to put it) but still didnt work...
Thanks for any help!
It works for the first initial bind but after it does not wok (when the property changes)
My Code:
My Binding:
//databinding
Binding(newProjectile.CurrentVelocity, lbl_angleoftraveloutput, "AngleOfTravel");
private void Binding(velocity Object, Label Output, string Field)
{
Binding newBinding = new Binding();
newBinding.Source = Object;
newBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
newBinding.Path = new PropertyPath(Field);
newBinding.Mode = BindingMode.TwoWay;
Output.SetBinding(Label.ContentProperty, newBinding);
}
My Object (Part of it):
public class velocity : INotifyPropertyChanged, ICloneable
{
public double AngleOfTravel
{
get { return _AngleOfTravel; }
set
{
_AngleOfTravel = value;
OnPropertyChanged("AngleOfTravel");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string PropertyName)
{
PropertyChangedEventHandler Handler = PropertyChanged;
if (Handler != null)
{
Handler(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
I have analyzed your code. In the button click event - LoadStaticsAndStart you have a statement newParticle.CurrentVelocity = newParticle.InitialVelocity; whic his causing the issue. newParticle.CurrentVelocity is bound to the UI Labels so it will have INotifyPropertyChanged Implementation. But newParticle.InitialVelocity is not bound with UI so it will not have INotifyPropertyChanged so I have commented the line and the UI update for you output labels are working now. Replace the function with the below function.
private void LoadStaticsAndStart(object sender, RoutedEventArgs e)
{
GraphCanvas.Visibility = Visibility.Visible;
DrawAxis();
List<velocity> velocityList = new List<velocity>();
double[,] DisplacementArray = new double[59, 1];
btn_Calculate.Click += OnLoaded;
particle newParticle;
velocity InitialVelocity = new velocity();
ObjectsAndDataStuctures.Environment newEnvironment;
if (FormatCheck(txtb_AngleOLaunch) == false || FormatCheck(txtbox_InitialVelocity) == false || FormatCheck(txtbox_TimeOfFlight) == false)
{
MessageBox.Show(" Input is not valid");
}
else
{
newEnvironment = new ObjectsAndDataStuctures.Environment();
newEnvironment.gravity = -9.8; // gravity remove when needed or changed
InitialVelocity.Magnitude = Convert.ToDouble(txtbox_InitialVelocity.Text); // collecting variables.
InitialVelocity.AngleOfTravel = Convert.ToDouble(txtb_AngleOLaunch.Text); // collecting variables.
newParticle = new particle(InitialVelocity, Convert.ToDouble(txtbox_TimeOfFlight.Text));
stk_pnl_inputvar.Visibility = Visibility.Collapsed;
CreateLabels(newParticle);
newParticle.CalculateHComponent();
newParticle.CalculateVComponent();
MPP(newParticle, newEnvironment);
OnLoaded(this, e);
if (Convert.ToDouble(txtbox_TimeOfFlight.Text) > 60)
{
TimeConstant = Convert.ToDouble(txtbox_TimeOfFlight.Text) / 60;
}
else
{
TimeConstant = 1;
}
double HVelTemp = newParticle.InitialVelocity.HorizontalVelocity * PixelPerMeter;
double VVelTemp = newParticle.InitialVelocity.VerticalVelocity * PixelPerMeter * -1;
Velocity = new Vector(HVelTemp, VVelTemp); // y direction is downwards
acceleration = new Vector(0, -1 * newEnvironment.gravity * PixelPerMeter); // y direction is downwards
aTimer = new System.Windows.Threading.DispatcherTimer();
aTimer.Interval = new TimeSpan(0, 0, 0, 0, 200);
//newParticle.CurrentVelocity = newParticle.InitialVelocity;
//this.DataContext = this;
TimerEvent = (s, t) => onTimedEvent(sender, t, newParticle, newEnvironment);
aTimer.Tick += TimerEvent;
ListOfVelocities.Add((velocity)newParticle.CurrentVelocity.Clone());
InSimulation = true;
aTimer.Start();
}
}
if you want to assign you InitialVelocity with CurrentVelocity Assign property by property. Like this below
newParticle.CurrentVelocity.AngleOfTravel = newParticle.InitialVelocity.AngleOfTravel;
newParticle.CurrentVelocity.HorizontalVelocity = newParticle.InitialVelocity.HorizontalVelocity;
newParticle.CurrentVelocity.Magnitude = newParticle.InitialVelocity.Magnitude;
newParticle.CurrentVelocity.VerticalVelocity = newParticle.InitialVelocity.VerticalVelocity;

System.InvalidOperationException in C# WPF

I want to move a rectangle in WPF application using the following code. However, I am getting the following error:
System.InvalidOperationException: Cannot use a DependencyObject that belongs to a different thread
I looked at other problems in stackoverflow but nothing worked.
public partial class MainWindow : Window
{
private Rectangle rect;
int count = 1;
Timer timer;
public MainWindow()
{
InitializeComponent();
Rectangle movedRectangle = new Rectangle();
movedRectangle.Width = 200;
movedRectangle.Height = 50;
movedRectangle.Fill = Brushes.Blue;
movedRectangle.Opacity = 0.5;
TranslateTransform translateTransform1 = new TranslateTransform(50, 20);
movedRectangle.RenderTransform = translateTransform1;
this.can.Children.Add(movedRectangle);
this.rect = movedRectangle;
timer = new Timer(500);
timer.Elapsed += OnTimedEvent;
timer.Enabled = true;
}
private void OnTimedEvent(Object source, ElapsedEventArgs e)
{
count++;
TranslateTransform translateTransform1 = new TranslateTransform(50 + count * 2, 20);
this.rect.Dispatcher.Invoke(new Action(()=>
rect.RenderTransform = translateTransform1));
//this.can.UpdateLayout();
this.can.Dispatcher.Invoke(new Action(()=>
this.can.UpdateLayout()
));
}
I would suggest you to use DispatcherTimer than a normal timer.
Please see the below solution. enjoy.
Note: for DispatcherTimer you will need to add the assembly reference for System.Windows.Threading
public partial class MainWindow : Window
{
private Rectangle rect;
int count = 1;
private DispatcherTimer timer = null;
public MainWindow()
{
InitializeComponent();
Rectangle movedRectangle = new Rectangle();
movedRectangle.Width = 200;
movedRectangle.Height = 50;
movedRectangle.Fill = Brushes.Blue;
movedRectangle.Opacity = 0.5;
TranslateTransform translateTransform1 = new TranslateTransform(50, 20);
movedRectangle.RenderTransform = translateTransform1;
this.can.Children.Add(movedRectangle);
this.rect = movedRectangle;
timer = new DispatcherTimer();
timer.Interval = new TimeSpan(0, 0, 0, 0, 500);
timer.Tick += timer_Tick;
timer.Start();
timer.IsEnabled = true;
}
void timer_Tick(object sender, EventArgs e)
{
count++;
TranslateTransform translateTransform1 = new TranslateTransform(50 + count * 2, 20);
Dispatcher.BeginInvoke(new Action<TranslateTransform>(delegate(TranslateTransform t1)
{
rect.RenderTransform = t1;
this.can.UpdateLayout();
}), System.Windows.Threading.DispatcherPriority.Render, translateTransform1);
}
}
You are constructing a TranslateTransform (which is a DependencyObject) outside the UI thread. Easy fix:
this.rect.Dispatcher.Invoke(new Action(
() =>
{
TranslateTransform translateTransform1 = new TranslateTransform(50 + count * 2, 20);
rect.RenderTransform = translateTransform1;
}));
Arguably a better fix: use a DispatcherTimer instead and get rid of all your Dispatcher.Invoke calls.

timer / stopwatch for automation

I have a circular gauge(progress bar) with a polygon needle that when i select a number in a list box the needle goes up to that degree to resemble a speed in a car(works just fine)
What i want to do is automatically run through the indexes of the list items(0,1,2,3,....60) so the needle slowly rises till it gets to say 60 speed. Then, hold that for as long as i want so i can move a tack needle and run an odometer accordingly. i have tried to use a timer in a MVC class and use a stop watch. I can select the index 0 -6 but it will only just jump to the last one. I am trying to simulate a car dashboard the best i can...What are your thought?
public partial class MainWindow : Window
{
//DispatcherTimer timer;
List<Double> _items = new List<Double>();
DispatcherTimer timer;
public MainWindow()
{
this.InitializeComponent();
listBox1.ItemsSource = new List<double>() { 0,1,2,3,4,5,10,11,12,13,14, 15, 20, 25, 30, 35, 40 };
checkBox1.IsChecked = true;
park.Foreground = Brushes.Red;
timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(2500);
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{
int second = DateTime.Now.Second;
firstDigitsec = second / 10;
int secondDigitsec = second % 10;
checkBox1.IsChecked = false;
first.Foreground = Brushes.Green;
park.Foreground = Brushes.White;
checkBox2.IsChecked = true;
Stopwatch stopwatch = Stopwatch.StartNew();
listBox1.SelectedIndex = 0;
listBox1.SelectedIndex = 1;
listBox1.SelectedIndex = 2;
listBox1.SelectedIndex = 3;
}
private int _stopw;
public int sw
{
get { return _stopw; }
set
{
_stopw = value;
OnPropertyChanged("");
}
}
private int _firstDigitsec;
public int firstDigitsec
{
get { return _firstDigitsec; }
set
{
_firstDigitsec = value;
OnPropertyChanged("");
/*
if (firstDigitsec < 1)
{
listBox1.SelectedIndex = 0;
}
if (firstDigitsec < 2)
{
*/
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
Since you are using WPF this sounds like an excellent candidate for Animation
Use a Binding to bind your Gauge's Progress property to the Progress property in your viewmodel and apply a DoubleAnimation to animate the Progress property from the previous value to the new value

Categories

Resources