I have created a wallboard application for a service desk team, which uses WPF for front-end and the Cisco database of the phones in the back-end. The application is made of two screens that show different information, and these are displayed in the same screen and change between each other with a System.Timers.Timer.
The application is made so that if WindowA is visible, WindowB is shown and then WindowA is hidden. The moment one of the Windows becomes visible, that Window's timer become active again which resumes the database calls, while the other Window's timer becomes disabled:
private static void InterfaceChanger_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (WindowA.Visibility == Visibility.Visible)
{
WindowAEnabled = false;
ChangeVisibility(Visibility.Visible, WindowB);
WindowBEnabled = true;
WindowB_Elapsed(null, null); // force the call of the timer's callback
ChangeVisibility(Visibility.Collapsed, WindowA);
}
else
{
WindowBEnabled = false;
ChangeVisibility(Visibility.Visible, WindowA);
WindowAEnabled = true;
WindowA_Elapsed(null, null); // force the call of the timer's callback
ChangeVisibility(Visibility.Collapsed, WindowB);
}
}
private static void ChangeVisibility(Visibility visibility, Window window)
{
window.Dispatcher.Invoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
{
window.Visibility = visibility;
}, null);
}
The problem is that this works perfectly... at most 90% of the time. The problem is that sometimes, if for example WindowA's visibility is changed to Visible and WindowB's visibility is changed to Collapsed, WindowB collapses but WindowA takes 2-3 seconds to become visible, while most times WindowA becomes visible and it's not seen when WindowB collapses. This (when it doesn't work) results in the Desktop being visible instead of the application.
I originally used DispatcherPriority.Background but that resulted in the screen changer working 70-80% of the time, so I decided to change it for DispatcherPriority.Normal (DispatcherPriority.Sendresults basically in the same situation as Normal).
Questions:
Is this the normal behavior to be expected by the Dispatcher, taking into account this is running in x64 mode in a quad-core CPU?
Knowing that the queries are performed in async methods not awaited, shouldn't the Dispatcher take priority over the methods?
Is there another way (without using the Dispatcher, or using another Window property) to accomplish what I'm looking for?
This is the code used to access/start the Windows:
//WindowA:
<Application x:Class="MyNamespace.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="WindowA.xaml">
//WindowA class:
public static WindowA WindowAInstance;
public WindowA()
{
// unnecessary code hidden
WindowAInstance = this;
WindowB b = new WindowB;
}
// WindowB class
public static WindowB WindowBInstance;
public WindowB()
{
// unnecessary code hidden
WindowBInstance = this;
}
// this is the code that starts the timers
public static void StartTimersHandling()
{
Database.RemoveAgents();
InterfaceChangerTimer = new System.Timers.Timer();
InterfaceChangerTimer.Interval = ApplicationArguments.InterfaceChangerTime;
InterfaceChangerTimer.Elapsed += InterfaceChanger_Elapsed;
InterfaceChangerTimer.AutoReset = true;
InterfaceChangerTimer.Start();
WindowATimer = new System.Timers.Timer();
WindowATimer.Interval = 1000;
WindowATimer.Elapsed += WindowATimer_Elapsed;
WindowATimer.AutoReset = true;
WindowATimer.Start();
WindowBTimer = new System.Timers.Timer();
WindowBTimer.Interval = 1000;
WindowBTimer.Elapsed += WindowBTimer_Elapsed;
WindowBTimer.AutoReset = true;
WindowBTimer.Start();
}
It sounds like you're writing a kiosk application (ie. full-screen, non-interactive). If this is the case I think you would be better off having a single window and switching the views inside it, rather than switching between two separate windows. Also, you need to separate the database query work from the refreshing of the window content. Furthermore, I think it would help if the views knew nothing about each other: at the moment your first window is tightly coupled to your second, which is not really a good idea.
In my opinion, if you changed your architecture a little, a lot of the problems you are having would disappear. Here's what I would recommend:
First, just go with a single window. Create two user controls (Project > Add User Control), and move your XAML layout from your existing windows into these two new controls. Then make your main window look something like this:
<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:StackOverflow"
WindowState="Maximized" WindowStyle="None">
<Grid>
<my:UserControl1 x:Name="_first" Panel.ZIndex="1" />
<my:UserControl2 Panel.ZIndex="0" />
</Grid>
<Window.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard AutoReverse="True" RepeatBehavior="Forever">
<ObjectAnimationUsingKeyFrames BeginTime="0:0:5" Duration="0:0:5"
Storyboard.TargetName="_first"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0"
Value="{x:Static Visibility.Hidden}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>
</Window>
This is a full-screen window with no chrome that contains your two user controls (essentially the contents of your existing windows). They are layered in a Grid element so that one sits on top of the other: I'm using the Panel.ZIndex property to force the first control to the top of the pile. Finally, I'm using an animation (triggered when the window loads) that toggles the visibility of one of the controls to hide it after a certain period of time. The animation is set to repeat and auto-reverse, the effect of which is to hide one of the controls, then make it visible again. You can change the Duration attribute value to control how long each control "stays" visible; it's set to 5 seconds in this example, which means a 10 second delay between switches.
The key to this working is that the first user control, when visible, must fully obscure the other user control that lies beneath it. This is easy to accomplish by setting the background colour of the control.
Your user controls can contain anything that a window would contain. Here's the example user control XAML that I used:
<UserControl x:Class="StackOverflow.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="White" Padding="40">
<TextBlock Text="{Binding Number}" FontSize="60"
TextAlignment="Center" VerticalAlignment="Top" />
</UserControl>
As you can see it's just a TextBlock element whose Text property binds to a Number property defined in the user control's code-behind. I used the same XAML for both user controls, just varying the VerticalAlignment of the text so that I could tell which control was visible at any given time.
The code-behind looks like this (it's the same for both, with the exception of the class name):
using System;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Threading;
namespace StackOverflow
{
public partial class UserControl1 : UserControl, INotifyPropertyChanged
{
public UserControl1()
{
InitializeComponent();
DataContext = this;
_timer = new DispatcherTimer
{ Interval = TimeSpan.FromSeconds(5), IsEnabled = true };
_timer.Tick += (sender, e) => Task.Run(async () => await DoWorkAsync());
}
readonly DispatcherTimer _timer;
readonly Random _random = new Random();
public event PropertyChangedEventHandler PropertyChanged;
public int Number
{
get
{
return _number;
}
private set
{
if (_number != value)
{
_number = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Number"));
}
}
}
}
int _number;
async Task DoWorkAsync()
{
// Asynchronous code started on a thread pool thread
Console.WriteLine(GetType().Name + " starting work");
_timer.IsEnabled = false;
try
{
await Task.Delay(TimeSpan.FromSeconds(_random.Next(4, 12)));
Number++;
}
finally
{
_timer.IsEnabled = true;
}
Console.WriteLine(GetType().Name + " finished work");
}
}
}
It basically contains a single Number property (which implements INotifyPropertyChanged) that gets incremented by a "worker" method. The worker method is invoked by a timer: here, I'm using a DispatcherTimer, but as I'm not changing any UI elements directly any of the .NET timers would have done.
The worker is scheduled to run on the thread pool using Task.Run, and then runs asynchronously. I'm simulating a long-running job by waiting for a period of time with Task.Delay. This worker method would be where your database query gets called from. You can vary the gap between successive queries by setting the timer's Interval property. There's nothing to say that the gap between queries need be the same as the refresh interval of your UI (ie. the speed at which the two views are switched); indeed, as your query takes a variable amount of time, syncing the two would be tricky anyway.
Try to use Dispatcher.CurrentDispatcher instead of window.Dispatcher and BeginInvoke:
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.DataBind, new Action(() =>
{
window.Visibility = visibility;
}));
Updated
Switch your timer to DispatcherTimer:
timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(5) };
timer.Tick += (sender, args) => InterfaceChanger_Elapsed();
timer.Start();
Related
I have this setup in my AvaloniaUI application:
<ScrollViewer VerticalScrollBarVisibility="Auto"
AllowAutoHide="True"
Name="MessageLogScrollViewer">
<TextBlock HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
TextWrapping="NoWrap"
Text="{Binding ReceivedMessages}"></TextBlock>
</ScrollViewer>
The TextBlock basically displays log messages and I would like it to autoscroll to the bottom, when I append new lines to the String bound to the text property.
The ScrollViewer has a method called ScrollToEnd(), that I would need to call whenever I update the Text. So I tried to define a function in my code behind like so:
private ScrollViewer messageLogScrollViewer;
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel(this);
messageLogScrollViewer = this.FindControl<ScrollViewer>("MessageLogScrollViewer");
}
...
public void ScrollTextToEnd()
{
messageLogScrollViewer.ScrollToEnd();
}
I then tried to call that function from my ViewModel:
private string receivedMessages = string.Empty;
public string ReceivedMessages
{
get => receivedMessages;
set => this.RaiseAndSetIfChanged(ref receivedMessages, value);
}
...
private MainWindow _window;
public MainWindowViewModel(MainWindow window)
{
_window = window;
}
...
ReceivedMessage += "\n";
ReceivedMessages += ReceivedMessage;
_window.ScrollTextToEnd(); // Does not work.
But unfortunately, this the ScrollToEnd() function needs to be called from the UI-Thread, because I get an Exception:
System.InvalidOperationException: 'Call from invalid thread'
My question is how can I autoscroll my ScrollViewer to the end, whenever I update the TextBlocks Text property via DataBinding?
Ok, I figured it out, but leaving this here for reference.
In order to execute the function from another thread on the UI-Thread one needs to call
Dispatcher.UIThread.InvokeAsync(_window.ScrollTextToEnd);
But this scrolled only to the penultimate line, so it seems, the control is not updated yet, when I call ScrollTextToEnd(). Therefore I added a short timeout in between updating the text and calling the scroll function like this
ReceivedMessages += ReceivedMessage;
// A short timeout is needed, so that the scroll viewer will scroll to the new end of its content.
Thread.Sleep(10);
Dispatcher.UIThread.InvokeAsync(_window.ScrollTextToEnd);
It works now just like that.
I've created an example to illustrate my problem.
ViewModel:
public class VM : INotifyPropertyChanged
{
private double _value = 1;
public double Value
{
get { return _value; }
set
{
_value = value;
OnPropertyChanged();
}
}
public VM()
{
var timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromTicks(1);
timer.Tick += (s, e) => { Value += 1; };
timer.Start();
}
// OnPropertyChanged stuff ...
}
}
View:
<Window.DataContext>
<namespace:VM/>
</Window.DataContext>
<Grid>
<TextBox Text="{Binding Value, IsAsync=True, FallbackValue=Test}"/>
</Grid>
When running my application the text in the textbox flickers. During the update process the FallbackValue is displayed, which makes no sense to me.
Does anyone knows the purposes or what are the benefits that during the update process the FallbackValue is displayed? Is there a way to display the old Value during an async update process?
This seems normal to me, given that you are using IsAsync=True in your binding. From the documentation:
While waiting for the value to arrive, the binding reports the FallbackValue, if one is available
When the PropertyChanged event is raised, WPF initiates the process of updating the target of the binding. Normally this would happen synchronously, with the property getter called immediately to update the value.
But you are using IsAysnc=True, so instead WPF fills in the target with the fallback value, and starts an asynchronous request to retrieve the actual property value later. Until that request has completed, the fallback value is displayed.
Does anyone knows the purposes or what are the benefits that during the update process the FallbackValue is displayed?
Per the documentation, the intent behind the IsAsync=True setting is that it's used when the property getter is, or could be, slow. Your code has told WPF that the property value has changed, so it knows the old value is no longer valid. Your code has also told (via the IsAsync in the XAML) that the property getter could take some time to provide the new value, so it defers retrieving that value until later.
In the meantime, what should WPF display? That's what the fallback value is there for.
Is there a way to display the old Value during an async update process?
If you don't want the behavior that is designed for this feature in WPF, you should just retrieve the new data asynchronously yourself, and update the property via the setter when you have it. It's not a good idea for a property getter to be slow anyway, so this would be a better design in any case.
I had the same problem but with an image source. I've removed the IsAsync on the binding and I have made my getter async:
// This field hold a copy of the thumbnail.
BitmapImage thumbnail;
// Boolean to avoid loading the image multiple times.
bool loadThumbnailInProgress;
// I'm using object as the type of the property in order to be able to return
// UnsetValue.
public object Thumbnail
{
get {
if (thumbnail != null) return thumbnail;
if (!loadThumbnailInProgress) {
// Using BeginInvoke() allow to load the image asynchronously.
dispatcher.BeginInvoke(new Action(() => {
thumbnail = LoadThumbnail();
RaisePropertyChanged(nameof(Thumbnail));
}));
loadThumbnailInProgress = true;
}
// Returning UnsetValue tells WPF to use the fallback value.
return DependencyProperty.UnsetValue;
}
}
Sometimes a binding will fail, failure is important to consider. Fallback value option presents users a message if an error occurs, rather than nothing happening. If you would like your fallbackvalue to display the previous value contained, I could think of a few ways of trying : possibly saving the value in a reference string and/or to another control, then binding to that control
But if you don't want the fallbackvalue displayed at all, you need to do a code inspection to see how your binding is failing/or is slow, and contain it in your code behind
I've found an approach to avoid flickering by just inheriting from textbox and overriding it's textproperty-metadata.
Custom TextBoxControl
public class CustomTextBox : TextBox
{
static CustomTextBox()
{
TextProperty.OverrideMetadata(typeof(CustomTextBox), new FrameworkPropertyMetadata(null, null, CoerceChanged));
}
private static object CoerceChanged(DependencyObject d, object basevalue)
{
var tb = d as TextBox;
if (basevalue == null)
{
return tb.Text;
}
return basevalue;
}
}
View
<Window.DataContext>
<namespace:VM/>
</Window.DataContext>
<Grid>
<namespace:CustomTextBox Text="{Binding Value, IsAsync=True}"/>
</Grid>
It's important to have the text-binding without a fallbackvalue. So during update process the text is set to the textproperty defalut value - so in this case to null.
The CoerceChanged handler checks whether the new value is null. If it's so he returns the old value so that during update process there is still the old value displayed.
I have a WPF application that receives messages from a server.
I listen for messages in a background worker and if a new messages arrives I call a new event.
In my ServerMessage.xaml I had a TextBox that was bound to a property in my ViewModel.
<TextBox IsReadOnly="True" VerticalScrollBarVisibility="Visible" Text="{Binding ServerMessages}" />
In the code-behind of my ServerMessage.xaml.cs I listen for somebody to call the server-message Event.
public void serverMessage(object sender, ServerMessageEventArgs e)
{
viewModel.ServerMessages += e.ServerMessage + "\r\n";
}
Due to certain circumstances I have to switch to a RichTextBox (mostly because of different text-color and images).
Sadly I can't bind anything to the RichTextBox. I tried some of the available Extensions for binding but they didn't work.
To get it working I used the RichTextBox.Dispatcher in the event. It looks like this:
RichTextBox.Dispatcher.BeginInvoke(
System.Windows.Threading.DispatcherPriority.Normal,
new Action(() =>
{
TextPointer caretPos = ChatTextBox.CaretPosition.DocumentEnd;
new InlineUIContainer(new TextBlock() { Text = e.ServerMessage }, caretPos);
RichTextBox.AppendText("\u2028");
RichTextBox.ScrollToEnd();
}
));
Is there a possibility to get rid of the Dispatcher and get back to binding?
Do I gain something if I use binding or is the Dispatcher okay, too?
I've got a Storyboard used in my Windows Phone app:
<Canvas x:Name="myCanvas" Grid.Row="1">
<Canvas.Resources>
<Storyboard x:Name="sb">
<ColorAnimationUsingKeyFrames
Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)"
AutoReverse="True">
<EasingColorKeyFrame KeyTime="00:00:0" Value="Black" />
<EasingColorKeyFrame KeyTime="00:00:0.25" Value="Red" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</Canvas.Resources>
</Canvas>
I've got multiple Rectangles that will be using this Storyboard, but it only seems to work the first time.
For example, the following code is used to display four different Rectangles but only the first one is displayed. The code does not error, but the last 3 Rectangles do not turn red and it appears the Storyboard doesn't even run.
sb.Stop();
sb.SetValue(Storyboard.TargetNameProperty, myRect1.name);
sb.Begin();
sb.Stop();
sb.SetValue(Storyboard.TargetNameProperty, myRect2.name);
sb.Begin();
sb.Stop();
sb.SetValue(Storyboard.TargetNameProperty, myRect3.name);
sb.Begin();
sb.Stop();
sb.SetValue(Storyboard.TargetNameProperty, myRect4.name);
sb.Begin();
Can anyone see what I'm doing wrong, or know how to get my Storyboard to be re-usable?
First, what you're doing wrong:
The storyboard execution is asynchronous. When you call the Storyboard.Begin method, the storyboard begins in background and your code continues to execute. Therefore, You're calling Storyboard.Stop right after starting it! The only one you don't stop is the last one, and that's why it's the only rectangle which color changes.
If you want to chain your animations, you have to subscribe to the Completed event to know when the storyboard ends, then restart it for the next controls. Here is one way of doing that:
private Rectangle[] ControlsToAnimate;
private int CurrentIndex;
private void Button_Click(object sender, RoutedEventArgs e)
{
this.ControlsToAnimate = new[] { this.Rectangle1, this.Rectangle2 };
this.Storyboard1.Completed += StoryboardCompleted;
this.AnimateNextControl();
}
private void AnimateNextControl()
{
if (this.CurrentIndex >= this.ControlsToAnimate.Length)
{
this.CurrentIndex = 0;
return;
}
var nextControl = this.ControlsToAnimate[this.CurrentIndex];
this.CurrentIndex++;
this.Storyboard1.Stop();
this.Storyboard1.SetValue(Storyboard.TargetNameProperty, nextControl.Name);
this.Storyboard1.Begin();
}
private void StoryboardCompleted(object sender, EventArgs e)
{
this.AnimateNextControl();
}
Now, you're going to face two problems:
When assigning the storyboard to a new control, the color of the previous control will return to its original value (before the storyboard was started). If you want it to keep its new value, you've got to save it:
private void AnimateNextControl()
{
if (this.CurrentIndex > 0)
{
var brush = (SolidColorBrush)this.ControlsToAnimate[this.CurrentIndex - 1].Fill;
brush.Color = brush.Color;
}
if (this.CurrentIndex >= this.ControlsToAnimate.Length)
{
this.CurrentIndex = 0;
return;
}
var nextControl = this.ControlsToAnimate[this.CurrentIndex];
this.CurrentIndex++;
this.Storyboard1.Stop();
this.Storyboard1.SetValue(Storyboard.TargetNameProperty, nextControl.Name);
this.Storyboard1.Begin();
}
You can't animate two controls at the same time using a single storyboard. If you want to animate all your rectangles at the same time, you need to use one storyboard per control.
Once again I must make my comment an answer since I still cannot comment apparently.
Are you attempting to run these all concurrently? I do believe that only a single instance of a XAML defined storyboard exists so you may not be able to reuse it on multiple controls at the same time.
If all else fails you can just create a UserControl with a rectangle containing the storyboard which you CAN reuse. That code sample I provided on your previous question is from a Tile that flips using a storyboard. Since its a usercontrol, I can have as many tiles as I want flipping at the same time.
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.