Windows Phone data binding: cross-thread - c#

we have a problem with data binding on windows phone (using xaml). i have created a simple example, which should allow to reproduce the problem.
Here is our model-class:
public class Data : INotifyPropertyChanged
{
private int value = 0;
public int Value
{
get
{
return value;
}
set
{
this.value = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Value"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public Data()
{
var t = new Thread(new ThreadStart(() =>
{
while (true)
{
Thread.Sleep(2000);
Value += 1;
}
}));
t.IsBackground = true;
t.Start();
}
}
which uses a thread to update the value-property and fire the PropertyChanged-event.
Now i want to bind this value-property to a gui control:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<TextBlock Text="{Binding Path=Value}" />
</Grid>
public MainPage()
{
InitializeComponent();
DataContext = new Data();
}
when the value first changes (and the PropertyChanged-event gets fired) the data binding system tries to copy the value of Data.Value to TextBlock.Text, which results in an invalid cross-thread exception, as this event is not fired on the ui thread.
my question: shouldn't the .NET databinding framework recognize that i'm binding to a ui control and perform the thread switching itself? i know that i can simply use a dispatcher to fire the PropertyChanged-event on the main thread, but i'd like to have my model-class more seperated from the gui component.
is there a better solution to this problem? i am unable to use the DependencyObject approach, because our core project (which contains the model class) should run on Windows Phone AND Android, and Android doesn't support the System.Windows-namespace.

One way to solve this would be to store a reference to the dispatcher on your view model and only use it to execute the property changed event if it is not null. Then you can set the dispatcher property in your VM's constructor.

I do like this:
public event PropertyChangedEventHandler PropertyChanged;
private void PropertyEventChanged(string propertyName)
{
if (PropertyChanged == null) return;
if (Application.OpenForms.Count > 0 && Application.OpenForms[0].InvokeRequired)
Application.OpenForms[0].Invoke(new MethodInvoker(() => PropertyChanged(this, new PropertyChangedEventArgs(propertyName))));
else
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

Related

Creating view on receiving a message

I have a MVVM view and viewmodel. In the constructor of my viewmodel I pass a list of IObservable messages and subscribe to them through a simple class sitting outide of my viewmodel and view
Outside class
{
viewModel =
new ViewModelClass(
responseHandler.AsObservable());
viewModel.PropertyChanged += ViewModel_PropertyChanged;
}
private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(ViewModelClass.MyProperty))
{
// Error here
view = new MyViewClass() { DataContext = viewModel };
}
}
In the view model constructor
subscription = receiveMessages.Subscribe(MessageReceived);
private void MessageReceived(GvsMessage message)
{
MyProperty = true;
}
On receiving a message I want to create my view not before that. Although the viewmodel is created before to handle property change etc
The problem is that I get "the calling thread must be sta because many ui components require this". Could someone please help
As we spoke in the comments you need to use a Dispatcher which can be acquired from different parts of the app.
To initialize the dispatcher you can use this snippet:
protected static Dispatcher _d;
if (Application.Current != null)
{
_d = Application.Current.Dispatcher;
}
else
{
_d = Dispatcher.CurrentDispatcher;
}
Explanation:
The first dispatcher is done when the application has UnitTests when you run the tests this dispatcher will not be null,
The second one is the current Dispatcher used by your application.
After you have this in your code when initializing your VM now you can send messages Actions, Events to the UI Thread.
I have a little method just for that:
public static void UIThread(Action action)
{
if (_d == null)
{
if (Application.Current != null)
{
_d = Application.Current.Dispatcher;
}
else
{
_d = Dispatcher.CurrentDispatcher;
}
}
_d.Invoke(action);
}
This function will accept lambda, like so:
UIThread(() =>
{
Processing = true;
Message = "Working ...";
//in your case you would raise the Loaded event here
});
This way you EventHandler in your view will have no problem showing that view.
If you need any more info let us know.
HTH
I resolved this by creating the view and the viewmodel in the constructor. In the propertychanged event I just set a property IsVisible to 'true' binds the window visibility
<Window.Visibility>
<Binding Path="IsVisible" Converter="{StaticResource BoolToVisibilityConverter}" Mode="TwoWay"/>
</Window.Visibility>

WPF: UI not being updated with INotifyPropertyChanged

I'm currently learning WPF, DataContexts & DataBinding. My goal is to have a Taskbar task (using NotifyIconWpf) that has a continuous thread running the background to monitor a network.
I've managed to get a UI element (shown in screenshot) bound to the ProgramClock class, but it does not update when the ProgramClock changes, most likely because something in the INotifyPropertyChanged parameters are wrong.
The closest similar problem I've found is UI not being updated INotifyPropertyChanged however I haven't been able to figure out what to change the DataPath in the XAML, or how to make INotifyPropertyChanged work properly.
Note that the BackgroundWorker thread successfully updates the App's static ProgramClock (checked with a separate WinForm) and that time is initially loaded in the WPF, so it's probably the PropertyChanged not being called properly.
ProgramClock
public class ProgramClock : INotifyPropertyChanged
{
private DateTime _myTime;
public event PropertyChangedEventHandler PropertyChanged;
private ClockController clockController;
public ProgramClock()
{
this._myTime = DateTime.Now;
clockController = new ClockController();
MessageBox.Show("created new clock");
}
public DateTime MyTime
{
get
{
return this._myTime;
}
set
{
if (_myTime == value) return;
_myTime = value;
//System.Windows.Forms.MessageBox.Show(PropertyChanged.ToString());
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(_myTime.ToString()));
}
}
public string MyTimeString
{
get { return this._myTime.ToString(); }
}
public void UpdateTime()
{
this.MyTime = DateTime.Now;
}
}
Bubble CS
public partial class InfoBubble : System.Windows.Controls.UserControl
{
public InfoBubble()
{
InitializeComponent();
this.DataContext = App.ClockBindingContainer;
}
}
Bubble XAML
<UserControl x:Class="FileWatcher.Controls.InfoBubble"
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">
<Border
Background="White"
BorderBrush="Orange"
BorderThickness="2"
CornerRadius="4"
Opacity="1"
Width="160"
Height="40">
<TextBlock
Text="{Binding Path=MyTimeString}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
</UserControl>
Main app
public partial class App : System.Windows.Application
{
private TaskbarIcon tb;
private ResourceDictionary _myResourceDictionary;
public static ProgramClock _programClock = new ProgramClock();
private void Application_Startup(object sender, StartupEventArgs e)
{
NotifIconStarter();
}
public static ProgramClock ClockBindingContainer
{
get { return _programClock; }
}
}
One problem is in your invocation of the PropertyChanged event. You need to pass the name of the property that is changing to the PropertyChangedEventArgs not the new value.
So use:
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("MyTime"));
instead of:
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(_myTime.ToString()));
However, you are actually binding to another property - MyTimeString.
Ultimately the property you are binding to needs to raise the event.
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(_myTime.ToString()));
You should pass property name:
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("MyTime");
However I suggest you to get PostSharp library - it has nice features that enable you to write normal properties and "decorate it by attribute" with automatic raising of PropertyChanged. If you do not want to use PostSharp at least create some method like:
public void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
and call it in your setter. ([CallerMemberName] is C# 5.0 feature which automatically pass "caller" name (in setter it will pass property name )
You are not notifying the change in the property you are binding to (which is MyTimeString), so WPF knows MyTime changes, but does MyTimeString change too? was never notified.
Try to change this:
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(_myTime.ToString()));
To this:
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("MyTimeString")); // Not MyTime!

How to rise PropertyChanged from background thread [duplicate]

This question already has answers here:
Property is not updating in textbox - WPF
(2 answers)
Closed 8 years ago.
Question was answered in other my thread, but i dont get full answer. I made some walk around and its work but im not very happy with this.
So im asking how to do it in MVVM patern.
I got very frustrating problems with updating value in my textbox in View. Proprerty Message is updating from my callback but its not displaying in GUI. Property is correctly binding. There is noway i can do this. The reason is im not raising PropertyChanged on the UI thread, since im calling it from a callback. I'm trying, but nothing works. How to raise PropertyChange event on UI thread?
EDIT AGAIN REFRESH CODE:
namespace test
{
public class MainViewModel : INotifyPropertyChanged, IDataExchangeCallback
{
public MainViewModel()
{
Message = "TEST1";
}
void IDataExchangeCallback.Result(string result)
{
Message += result;
}
public void Register()
{
InstanceContext instanceContext = new InstanceContext(new MainViewModel());
DataExchangeClient client = new DataExchangeClient(instanceContext);
client.RegisterClient(Guid.NewGuid().ToString());
}
private string _message;
public string Message
{
get { return this._message; }
set
{
if (this._message != value)
{
this._message = value;
OnPropertyChanged("Message");
}
}
}
public event PropertyChangedEventHandler PropertyChanged; //ALWAYS NULL!!! BUT I HAVE INotifyPropertyChanged implement
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
Application.Current.Dispatcher.Invoke(() =>
handler(this, new PropertyChangedEventArgs(name)));
}
}
}
}
My PropertChange is always null. WHY? It shoud subscribe to PropertyChanged when i'm using bindings.
EDIT
I Still Need Help
Found solution!
Apparently if you want use INotifyPropertyChanged you need to call it explicitly in code.
this.DataContext = mvm;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MainViewModel mvm = new MainViewModel();
mvm.Register();
this.DataContext = mvm; //this you need
}
}
Declaration in XAML is not sufficient:
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
If someone can explain me why I would be grateful?

MVVM updating Label

I know this is a frequently asked question, but I'm trying to solve it at least a week now... Read so many Threads, downloaded millions of different MVVM-Pattern-Examples and so on...
I just want to update a stupid label in my MVVM modelview first approach:
void StartUpProcess_DoWork(object sender, DoWorkEventArgs e)
{
SplashWindow splash = new SplashWindow();
var ViewModel_Splash = new VM_SplashWindow();
splash.DataContext = ViewModel_Splash;
splash.Topmost = true;
splash.Show();
ViewModel_Splash.DoWork();
}
The complete ViewModel:
public class VM_SplashWindow:VM_Base
{
#region Properties
private string _TextMessage;
public string TextMessage
{
get
{
return _TextMessage;
}
set
{
if(_TextMessage != value)
{
_TextMessage = value;
base.OnPropertyChanged("TextMessage");
}
}
}
#endregion
#region Methods
public void DoWork()
{
this.TextMessage = "Initialize";
for(int aa = 0; aa < 1000; aa++)
{
this.TextMessage = "Load Modul: " + aa.ToString();
Thread.Sleep(5);
}
this.TextMessage = "Done";
Thread.Sleep(1000);
}
#endregion
}
A small piece from the base:
public abstract class VM_Base:INotifyPropertyChanged, IDisposable
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion
}
And finally the view:
<Label Height="28" Margin="19,0,17,15" Name="label2" VerticalAlignment="Bottom"
Content="{Binding Path=TextMessage}" Foreground="White" />
If I set a initial value for the TextMessage Property in the constructor of my viewmodel, this initial value will be shown after the splash.Show() command.
Setting the TextMessage Property in the DoWork-Method raises the onPropertyChangedEvent but unfortunately it will not update the label in the window. I don't know what I should do... I'm really looking forward for help. Many thanks in advance!
maybe I should mention that the StartUpProcess_DoWork is running in a own STAThread
kind regards, flo
Apparently, you are performing a lot of work in the GUI thread. And with Thread.Sleep you even suspend the GUI thread. Therefore, it will not be able to update the controls.
The solution is to use a different thread for the DoWork method. This can be easily achieved with a BackgroundWorker. If you provide the GUI dispatcher object to the worker, you can issue GUI changes from there. Although it would be better to use the ProgressChanged-Event for that, if it is possible.

wpf c# databinding on object

say I have this control:
public partial class bloc999 : UserControl
{
bloc999Data mainBlock = new bloc999Data();
public bloc999()
{
InitializeComponent();
mainBlock.txtContents = "100";
base.DataContext = mainBlock;
}
}
in the xaml:
<TextBox Margin="74,116,106,0" Name="txtContents"
Text="{Binding Path=txtContents, UpdateSourceTrigger=PropertyChanged,Mode = TwoWay}" />
<TextBox Margin="74,145,106,132" Name="txtContents2"
Text="{Binding Path=txtContents2, UpdateSourceTrigger=PropertyChanged,Mode = TwoWay}" />
Then I have this class:
public class bloc999Data : INotifyPropertyChanged
{
string _txtContents;
string _txtContents2;
public event PropertyChangedEventHandler PropertyChanged;
void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(
this, new PropertyChangedEventArgs(propName));
}
public string txtContents2
{
get
{
return this._txtContents2;
}
set
{
if (int.Parse(value) > int.Parse(this._txtContents))
{
this._txtContents2 = "000";
}
else
this._txtContents2 = value;
NotifyPropertyChanged("txtContents2");
}
}
public string txtContents
{
get
{
return this._txtContents;
}
set
{
this._txtContents = value;
NotifyPropertyChanged("txtContents");
}
}
}
Ok now say I have A button on the form and I do this in the code:
mainBlock.txtContents2 = "7777777";
It puts 000 in the textbox, but If i just type in manually, in the textbox (txtContents2), the setter code is called but for some reason the textboxes value does not change, the instance value does change. help?
I believe it's just because the value is changing within the context of the data binding operation, so WPF just ignores it because it knows the value is changing and thinks the event is superfluous. What it doesn't know is that you've gone and changed the value from the value WPF has to something else again.
If you do the notification in a separate message then WPF will process it outside the context of the current data binding operation and will thus pick up the change:
if (int.Parse(value) > int.Parse(this._txtContents))
{
this._txtContents2 = "000";
// notify WPF of our change to the property in a separate message
Dispatcher.BeginInvoke((ThreadStart)delegate
{
NotifyPropertyChanged("txtContents2");
});
}
else
{
this._txtContents2 = value;
NotifyPropertyChanged("txtContents2");
}
This assumes your view model has access to the Dispatcher. An example of how to do so is shown in my blog post on a base ViewModel class.
I was having similar problem earlier here
In your usercontrol, update Binding and set UpdateSourceTrigger to Explicit
<TextBox Margin="74,145,106,132" x:Name="txtContents2" TextChanged="txtContents2_TextChanged"
Text="{Binding Path=txtContents2, UpdateSourceTrigger=Explicit,Mode = TwoWay}" />
then in the TextChanged event handler update the binding manually by validating the input.
move validation logic from property txtContent2's setter in bloc999Data in this event handler
private void txtContents2_TextChanged(object sender, TextChangedEventArgs e)
{
if (int.Parse(txtContents2.Text) > int.Parse(mainBlock.txtContents))
{
mainBlock.txtContents2 = "000";
txtContents2.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
}
else
{
mainBlock.txtContents2 = txtContents2.Text;
txtContents2.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
}
and it works.
Hope it helps!!

Categories

Resources