I have not used wpf alot and thought it would be a simple proccess to change the colour of an ellipse at runtime. I have a FileWatcher and in the event created I want to change the colour of the ellipse to a colour and back again, creating a blinking effect. (created is the ellipse, br4 is a solid colour brush defined in xaml)
public void watcherCreated(object seneder, FileSystemEventArgs e)
{
Application.Current.Resources["br4"] = new SolidColorBrush(Colors.Green);
created.Fill = (SolidColorBrush)Application.Current.Resources["br4"];
}
as soon as a file is created in the path which fires the event i get this error: Invalid operation exception
The calling thread cannot access this object because a different thread owns it.
I have looked for a solution with the freeze() method, but with no success.
created.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(
delegate()
{
Application.Current.Resources["br4"] = new SolidColorBrush(Colors.Green);
created.Fill = (SolidColorBrush)Application.Current.Resources["br4"];
}
));
got it thanks for comments
You can only access UI elements from the same thread in which they were created.
You should use Dispatcher.Invoke or Dispatcher.BeginInvoke to have a delegate called on the UI thread...where you can then access the "created" elements' "Fill" property.
See this link for an explanation of the problem:
http://www.switchonthecode.com/tutorials/working-with-the-wpf-dispatcher
Instead of trying to set the changing colour in the UI...what you can do is expose a property on your ViewModel which holds a state.
When your FileWatcher notifies you of a newly created file (by calling your watcherCreated method) you just set that state in your ViewModel.
In your UI...use a Binding with a Converter to bind to the state property in your ViewModel. The converter will determine what Brush to use depending on the state e.g. if state is 1 return a Green brush, if state is 0 return a Red brush.
To reset the state back to the "off" position...you could have a timer that after say 1 second, etc...sets the state value back to off.
By doing this....you separate the state from the UI.
If in the future you wanted a more sophisticated way of showing the state in the UI...e.g. having an animation (using StoryBoards/Visual State Manager) that gradually fades away from Green back to Red...then you could have that animation triggered, once again based on the state in the ViewModel.
In WPF all the UI controls are loaded in a different thread while as your application runs in a separate thread.
So , think it as , you are getting this error because your application(Main Thread) is trying to access the Elipse that is in UIThread. And this is not allowed for as threads can not access each others object directly.
So WPF has introduced dispatcher object. Use the following
if (this.Dispatcher.Thread != System.Threading.Thread.CurrentThread)
{
this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
new Action(
delegate()
{
Application.Current.Resources["br4"] = new SolidColorBrush(Colors.Green);
created.Fill = (SolidColorBrush)Application.Current.Resources["br4"];
}
));
}
Even simpler solution is to set the created.Fill on the UI thread itself. you will not need Dispatcher.Invoke or Dispatcher.BeginInvoke.
Related
I am using custom themedictionary in my UWP app. I change the value of a ThemeResource at runtime. This change is reflected only in the mainview and not the other views. Even if i create a new view after changing the resource's value the new view uses only the intial value of the resource. Is there anything I'm doing wrong?
This is how I change my resource's value.
(Application.Current.Resources["BackgroundBrush"] as SolidColorBrush).Color = Windows.UI.Colors.Black;
My Secondary View's XAML:
<Grid Background="{ThemeResource BackgroundBrush}"/>
Even my main view has the same XAML.
Here's the complete project. Download Repo as zip
I'd think this is by design. When we create multiple windows for an app, each window behaves independently. Each window operates in its own thread. The Application.Resources used in each window is also independent.
Application.Resources is a ResourceDictionary object and ResourceDictionary class inherits from DependencyObject, so Application.Resources is also a DependencyObject instance.
All DependencyObject instances must be created on the UI thread that is associated with the current Window for an app. This is enforced by the system, and there are two important implications of this for your code:
Code that uses API from two DependencyObject instances will always be run on the same thread, which is always the UI thread. You don't typically run into threading issues in this scenario.
Code that is not running on the main UI thread cannot access a DependencyObject directly because a DependencyObject has thread affinity to the UI thread only. Only code that runs on the UI thread can change or even read the value of a dependency property. For example a worker thread that you've initiated with a .NET Task or an explicit ThreadPool thread won't be able to read dependency properties or call other APIs.
For more info, please see DependencyObject and threading under Remarks of DependencyObject.
So each Window has its own Application.Resources. In secondary view, the Application.Resources is re-evaluated from your ResourceDictionary. The BackgroundBrush would not be affected by the setting in main view as with the following code
(Application.Current.Resources["BackgroundBrush"] as SolidColorBrush).Color = Windows.UI.Colors.Black;
you are only changing the Application.Current.Resources instance associated with main view's Window.
If you want the secondary view to use the same brush as the main view, I think you can store this color in main view and then apply it when creating secondary view. For example, I add a static field named BackgroundBrushColor in App class and then use it like the following:
private void ThemeChanger_Click(object sender, RoutedEventArgs e)
{
App.BackgroundBrushColor = Windows.UI.Color.FromArgb(Convert.ToByte(random.Next(255)), Convert.ToByte(random.Next(255)), Convert.ToByte(random.Next(255)), Convert.ToByte(random.Next(255)));
(Application.Current.Resources["BackgroundBrush"] as SolidColorBrush).Color = App.BackgroundBrushColor;
}
private async Task CreateNewViewAsync()
{
CoreApplicationView newView = CoreApplication.CreateNewView();
int newViewId = 0;
await newView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
(Application.Current.Resources["BackgroundBrush"] as SolidColorBrush).Color = App.BackgroundBrushColor;
Frame frame = new Frame();
frame.Navigate(typeof(SecondaryPage), null);
Window.Current.Content = frame;
// You have to activate the window in order to show it later.
Window.Current.Activate();
newViewId = ApplicationView.GetForCurrentView().Id;
});
bool viewShown = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(newViewId);
}
I have changed the code to modify the resource in each view. if you set a breakpoint in ThemeChanged method in SecondaryPage.xaml.cs before changing the value you can see that the resource's value has already changed to the updated one. But its not reflecting in the view.
The problem here is because your ThemeChanged event is triggered in main view, so it's running in main view's thread, therefore the ThemeManager_ThemeChanged method you've used in SecondaryPage.xaml.cs will also running in main view's thread. This leads to the Application.Current.Resources in ThemeManager_ThemeChanged method still get the ResourceDictionary instance associated with main view. That's why the resource's value has already changed to the updated one and won't reflect in the view. To see this clearly, you can take advantage of Threads Window while debugging.
To solve this issue, you can use this.Dispatcher.RunAsync method to run your code in right thread like the following:
private async void ThemeManager_ThemeChanged(Utils.ThemeManager theme)
{
await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
((SolidColorBrush)Application.Current.Resources["BackgroundBrush"]).Color = theme.HighAccentColorBrush.Color;
});
}
However, with this you will still get an error like: "The application called an interface that was marshalled for a different thread." This is because SolidColorBrush class also inherits from DependencyObject. To solve this issue, I'd suggest changing HighAccentColorBrush type from SolidColorBrush to Color and then use it like:
private async void ThemeManager_ThemeChanged(Utils.ThemeManager theme)
{
await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
((SolidColorBrush)Application.Current.Resources["BackgroundBrush"]).Color = theme.HighAccentColorBrush;
});
}
Use the theming this way.
In you theme page on button click add this logic
void OnClicked(object sender, System.EventArgs e)
{
var btn = sender as Button;
MessagingCenter.Send(this, "ThemeButtonClicked", btn.BackgroundColor);
Application.Current.Properties["Theme"] = btn.BackgroundColor;
Application.Current.SavePropertiesAsync();
Navigation.PopAsync(true);
}
In all other pages subscribe to the above notification as shown below.
MessagingCenter.Subscribe<Theme, Color>(this, "ThemeButtonClicked", OnThemeChanged);
Add this below method to execute when subscriber notifies.
private void OnThemeChanged(Theme source, Color cl)
{
this.BackgroundColor = cl;
layout.BackgroundColor = cl;
}
So that all the open instances on the stack will also get the theme updated. Hope this answers your question.
This might be an easy/stupid question, but I cannot simply get the three things in the title to work. I am following the advice from the following SO answer:
WPF listbox dynamically populated - how to get it to refresh?
Create an ObservableCollection and set your ListBox.ItemsSource to that collection. Because the collection is observable, the ListBox will update as its contents change.
However, if your real operation is blocking the UI thread, this may
prevent WPF from updating the UI until the operation completes
(because the WPF data binding infrastructure doesn't get a chance to
run). So you may need to run your lengthy operation on a background
thread. In this case, you will not be able to update the
ObservableCollection from the background thread due to WPF
cross-threading restrictions (you can update properties, but not
collections). To get around this, use Dispatcher.BeginInvoke() to
update the collection on the UI thread while continuing your operation
on the background thread.
I created ObservableCollection and attached it to the ItemSource of my ListBox:
private ObservableCollection<Employee> employeeList;
searchList.ItemsSource = employeeList;
I create a thread and instruct it to run the function that populates the ObservableCollection (I am passing text string and ObservableCollection):
Thread thread = new Thread(() => ldapConnector.Search(textbox.Text, employeeList));
thread.Start();
ldapConnector is a separate class that I initialized, when I started the program. And then I get the following error (pointing at thread creation line):
The calling thread cannot access this object because a different
thread owns it.
As I understood from that post, I would need to run Begin.Invoke() method right after thread creation like this:
Dispatcher.BeginInvoke(new Action(()=>{
searchList.ItemsSource = employeeList;
}));
What I am doing wrong and how to fix it?
I have a page in a Windows Phone 8.1 app where I have a few components that should be able to have three different color states. They should either be red, blue or the current theme's foreground color.
Therefore, if my app is started using the Dark theme on the phone, and then the user steps out of the app and changes the Light theme, and steps in to my app again, I need to immediately change components that had the old theme's foreground color.
Since the components are supposed to change between different colors (where the theme's foreground color is just one of them) I can't set their Foreground to PhoneForegroundColor in XAML.
What I've done is to add a Resuming event listener that does this:
myTextBlock.Foreground = new SolidColorBrush((Color)Application.Current.Resources["PhoneForegroundColor"]);
But... the Resuming event is fired before the resources of Application.Current are updated, so I end up with the same color as before. If the user steps out again and in again, it'll work since Application.Current.Resources["PhoneForegroundColor"] was updated at some point after the Resuming event the previous time.
Question: When can I first read the updated Application.Current.Resources["PhoneForegroundColor"], since Resuming doesn't seem to be the right place?
Question: Alternatively, is there a way for myTextBlock to inherit another component's ForegroundColor (CSS-ish), so that I can change the myTextBlock.Foreground programatically between Red/Blue/Inherit without having to mind changes to the Phone Theme within my app's lifecycle?
Any suggestions appreciated!
Regarding your first question: The "Resume process" isn't officially documented, but I figured out the following:
Resume gets called on the UI thread. As it is a void returning method, the caller will just continue, when it has an await inside. If you marshall something into the UI thread, it will be in the dispatcher queue and therefor run after the current task (resume).
So I just made this (and it works^^):
private async void App_Resuming(object sender, object e)
{
var x1 = new SolidColorBrush((Color)Application.Current.Resources["PhoneForegroundColor"]);
Debug.WriteLine(x1?.Color.ToString());
// Await marshalls back to the ui thread,
// so it gets put into the dispatcher queue
// and is run after the resuming has finished.
await Task.Delay(1);
var x2 = new SolidColorBrush((Color)Application.Current.Resources["PhoneForegroundColor"]);
Debug.WriteLine(x2?.Color.ToString());
}
Regarding your second question: You could introduce a "ValueProvider" in your app.xaml, that registers for the resume event and just provides a dependency property with the current color.
You will still have to set that on any TextBlock you want to use that in, but at least directly in XAML. This might work for styles too, but did not try that.
Sample implementation....
Provider:
public class ColorBindingProvider : DependencyObject
{
public ColorBindingProvider()
{
App.Current.Resuming += App_Resuming;
}
private async void App_Resuming(object sender, object e)
{
// Delay 1ms (see answer to your first question)
await Task.Delay(1);
TextColor = new SolidColorBrush((Color)Application.Current.Resources["PhoneForegroundColor"]);
}
public Brush TextColor
{
get { return (Brush)GetValue(TextColorProperty); }
set { SetValue(TextColorProperty, value); }
}
public static readonly DependencyProperty TextColorProperty =
DependencyProperty.Register("TextColor", typeof(Brush), typeof(ColorBindingProvider), new PropertyMetadata(null));
}
App.xaml:
<Application.Resources>
<local:ColorBindingProvider x:Name="ColorBindingProvider" TextColor="{StaticResource PhoneForegroundBrush}" />
</Application.Resources>
MainPage.xaml:
<TextBlock Text="Hey ho let's go" Foreground="{Binding TextColor, Source={StaticResource ColorBindingProvider}}" />
In Windows Phone 8.1 you are able to determine the selected theme via Application.Current.RequestedTheme witch will return a value of the enum Windows.UI.Xaml.ApplicationTheme.
example:
public static string GetImagePath(){
// if the background color is black, i want a white image
if(Application.Current.RequestedTheme == ApplicationTheme.Dark)
return "ms-appx:///Assets/img_light.jpg";
// if the background color is white, i want a dark image
return "ms-appx:///Assets/img_dark.jpg";
}
side note: you are even able to change the selected theme with Application.Current.RequestedTheme
more details: https://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.application.requestedtheme
Register to App_Resuming didn't work for me because this event isn't raised when application is not suspended. I had to listen to Window.Current.CoreWindow.VisibilityChanged += CoreWindow_VisibilityChanged;
No need for Task.Delay with this solution.
I've got small WPF / MVVM example project with two visual elements (a ComboBox and a simple TextBlock). Both elements are bound to a property of my ViewModel:
Properties MainViewModel.cs
public const string WelcomeTitlePropertyName = "WelcomeTitle";
private string _welcomeTitle = string.Empty;
public string WelcomeTitle
{
get{ return _welcomeTitle;}
set
{
_welcomeTitle = value;
RaisePropertyChanged(WelcomeTitlePropertyName);
}
}
public const string PositionsPropertyName = "Positions";
private ObservableCollection<int> _positions = new ObservableCollection<int>();
public ObservableCollection<int> Positions
{
get{ return _positions; }
set
{
_positions = value;
RaisePropertyChanged(PositionsPropertyName);
}
}
Bindings MainWindow.xaml
<StackPanel>
<TextBlock Text="{Binding WelcomeTitle}"/>
<ComboBox ItemsSource="{Binding Positions}" />
</StackPanel>
Now I change both properties from a non UI thread like this (which is not allowed, as far as I know it):
System.Threading.ThreadPool.QueueUserWorkItem(delegate
{
int i = 0;
while(true)
{
Positions.Add(i); // Solution 1: this throws NotSupportedException
WelcomeTitle = i.ToString(); // Solution 2: this works
i++;
}
}, null);
Question:
Why does solution 1 throw a NotSupportedExpection (not allowed to change collection from non dispatcher thread) while solution 2 works as desired?
Now I change both properties from a non UI thread like this (which is
not allowed, as far as I know it)
In general, changing property values is perfectly fine no matter what thread you are on. Problems and restrictions may come up when changing a property has an "interesting" side effect.
In this case both of the properties being changed produce interesting side effects, and the difference in observed behavior is due to these side effects being handled (from framework code, which you do not get to see directly) in different ways.
Why does solution 1 throw a NotSupportedExpection (not allowed to
change collection from non dispatcher thread) while solution 2 works
as desired?
When a binding source's properties are changed the WPF binding system responds by making the corresponding updates to the UI; however, when the changes are made from a background thread then the binding system's event handler will also run in the background thread and will not be able to update the UI directly. This is true for both the cases in question here.
The difference is that for "simple" property value changes the binding system automatically detects that it's not responding to the change on the UI thread and dispatches the UI changes to the correct thread using Dispatcher.Invoke, but when the observable collection is modified this dispatch does not happen automatically. The result is that the code that updates the UI runs on the background thread and an exception is thrown.
The solution
There are two things either one of which can solve this problem:
Make the property change in the UI thread directly
If the change is made on the UI thread then any PropertyChanged handlers will also run on the UI thread, so they will be free to make any UI changes they want. This solution can be enforced in your own code and will never result in a problem, but if no UI changes are required the extra work of dispatching to the UI thread will have been done for no benefit.
Make sure the PropertyChanged handler dispatches any UI-related changes to the UI thread
This solution has the benefit that it only dispatches work on demand, but also the drawback that the event handler (which might not be your own code) must be explicitly programmed to make the dispatch. .NET already does this for plain properties, but not for ObservableCollection. See How do I update an ObservableCollection via a worker thread? for more information.
The simple Property binding is automatically dispatched to the GUI thread by WPF and can be changed from a non-UI thread. However, this is NOT true for collection changes (ObservableCollection<> BindingList<>). Those changes must happen the UI thread the control was created on.
If I remember correctly, this was not true (solution 2 did not work also) in the early years of WPF and .NET.
I have a windows form 'MyForm' with a text box that is bound to a property in another class 'MyData'. The Data source update mode is set to "On Property Change"
I used the VisualStudio IDE. It created the following code for the binding
this.txtYield.DataBindings.Add(new Binding("Text", this.BindingSourceMyDataClass, "PropertyInMyDataClass", true, DataSourceUpdateMode.OnPropertyChanged));
In the form constructor, after initialize values code was added to bind the MyData Class to the form
myDataClassInstantiated = new MyDataClass();
BindingSourceMyDataClass.DataSource = myDataClassInstantiated;
The INotifyProperty Interface has been implemented:
public double PropertyInMyDataClass
{
get { return _PropertyInMyDataClass; }
set
{
if (!Equals(_PropertyInMyDataClass, value))
{
_PropertyInMyDataClass = value;
FirePropertyChanged("PropertyInMyDataClass");
}
}
}
A background worker is used to run the calculations and update the property 'PropertyInMyDataClass'
I expected that the text box on the form would update automatically when the background worker completed. That didn't happen
If I manually copy assign the value from the property to the form text box, the value is displayed properly
this.txtYield.Text = String.Format("{0:F0}", myDataClassInstantiated.PropertyInMyDataClass);
I tried to add Refresh() and Update() to the MyForm.MyBackgroundWorker_RunWorkerCompleted method, but the data still is not refreshed.
If I later run a different background worker that updates different text boxes on the same form, the text box bound to PropertyInMyDataClass gets updated
I would appreciate suggestions that will help me to understand and resolve this databinding problem
The problem comes from a couple of angles. If you are running the process on a background thread, the background thread cannot directly access a control on your form (which lives in a different thread) directly, otherwise you will get an exception. You also cannot expect the UI thread to update based on states in the background thread, unless you wire it up to do so. In order to do this, you need to invoke a delegate on the main UI thread..
Place this code (modify it to update whatever control you want with whatever value type you want) on the UI form.
public void UpdateOutput(string text)
{
this.Invoke((MethodInvoker) delegate {
lstOutput.Items.Add(text);
});
}
Then you can call this function in your background worker thread. (assuming your background process function lives in the same form, you can call it directly), if not then you will need a reference to the UI form in the class that the background process runs in.