WCF Callback notify user interface - wpf

I have a wcf callback service and the following scenario:
A client send a request to the service, and modify the color of a rectangle in the database, the service notifies it that the color has changed, and I want now in the callback notified method, to color the rectangle that was clicked with the chosen color:
Here is the method which is invoked when I click on rectangle
private void ChangeRectangleState_Single(object sender, RoutedEventArgs e)
{
Path mc = (Path)sender;
String name = mc.Name;
flag_macaz = colorClass.getRectangleColor(mc.Name+"_a",rectangleServiceClient);
ColorClass.changeRectangleColor(flag_rectangle,macazServiceClient,mc.Name+"_a");
}
public void RectangleServiceCallback_ClientNotified(objectsender,Rectangle NotifiedEventArgs e)
{
String name = e.RectangleName;
object wantedNode_a = Window.FindName(e.RectangleName);
Path rectangle = wantedNode_a as Path;
if (e.RectangleColor == 1)
{
rectangle.fill=...
}
else
if (e.RectangleColor == 0)
{
rectangle.fill=...
}
}
But I get the error "The calling thread cannot access this object because a different thread owns it."
I have tried the idea from http://www.switchonthecode.com/tutorials/working-with-the-wpf-dispatcher but the client get blocked.
Does anybody has other idea?

The WCF thread can't call the UI thread directly.
You'll need to fire an event from the WCF thread and subscribe to it in the UI thread. Then in your UI event handler have something like:
this.albumArt.InvokeIfRequired(() => this.SetBackgroundColor());
where InvokeIfRequired is an extension method:
public static void InvokeIfRequired(this Control control, Action action)
{
if (control.InvokeRequired)
{
control.Invoke(action);
}
else
{
action();
}
}

Related

Winforms WebBrowser control without IE popups not appearing [duplicate]

I am trying to implement a simple web browser control in one of my apps. This is to help integrate a web app into a toolset i am creating.
The problem is, this web app absolutly loves popup windows....
When a popup is opened, it opens in an IE window which is not a child of the MDI Container form that my main window is part of.
How can i get any and all popups created by clicking links in my WebBrowser to be a child of my MDI container (similar to setting the MDIParent property of a form)?
Thanks in advance.
The web browser control supports the NewWindow event to get notified about a popup window. The Winforms wrapper however does not let you do much with it, you can only cancel the popup. The native COM wrapper permits passing back a new instance of the web browser, that instance will then be used to display the popup.
Taking advantage of this requires some work. For starters, use Project + Add Reference, Browse tab and select c:\windows\system32\shdocvw.dll. That adds a reference to the native COM interface.
Create a form that acts as the popup form. Drop a WebBrowser on it and make its code look similar to this:
public partial class Form2 : Form {
public Form2() {
InitializeComponent();
}
public WebBrowser Browser {
get { return webBrowser1; }
}
}
The Browser property gives access to the browser that will be used to display the web page in the popup window.
Now back to the main form. Drop a WebBrowser on it and make its code look like this:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
webBrowser1.Url = new Uri("http://google.com");
}
SHDocVw.WebBrowser nativeBrowser;
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
nativeBrowser = (SHDocVw.WebBrowser)webBrowser1.ActiveXInstance;
nativeBrowser.NewWindow2 += nativeBrowser_NewWindow2;
}
protected override void OnFormClosing(FormClosingEventArgs e) {
nativeBrowser.NewWindow2 -= nativeBrowser_NewWindow2;
base.OnFormClosing(e);
}
void nativeBrowser_NewWindow2(ref object ppDisp, ref bool Cancel) {
var popup = new Form2();
popup.Show(this);
ppDisp = popup.Browser.ActiveXInstance;
}
}
The OnLoad method obtains a reference to the native COM interface, then subscribes an event handler to the NewWindow2 event. I made sure to unsubscribe that event in the FormClosing event handler, not 100% sure if that's necessary. Better safe then sorry.
The NewWindow2 event handler is the crux, note that the first argument allows passing back an untyped reference. That should be the native browser in the popup window. So I create an instance of Form2 and Show() it. Note the argument to Show(), that ensures that the popup is an owned window. Substitute this as necessary for your app, I assume you'd want to create an MDI child window in your case.
Do beware that this event doesn't fire for the window displayed when Javascript uses alert(). The browser doesn't treat that window as an HTML popup and doesn't use a browser window to display it so you cannot intercept or replace it.
I found that the best way to do this was to implement/sink the NewWindow3 event
Add the reference to c:\windows\system32\shdocvw.dll as mentioned in the other answers here.
Add event handler
SHDocVw.WebBrowser wbCOMmain = (SHDocVw.WebBrowser)webbrowser.ActiveXInstance;
wbCOMmain.NewWindow3 += wbCOMmain_NewWindow3;
Event method
void wbCOMmain_NewWindow3(ref object ppDisp,
ref bool Cancel,
uint dwFlags,
string bstrUrlContext,
string bstrUrl)
{
// bstrUrl is the url being navigated to
Cancel = true; // stop the navigation
// Do whatever else you want to do with that URL
// open in the same browser or new browser, etc.
}
Set "Embed Interop Types" for the "Interop.SHDocVw" assembly to false
Set the "local copy" to true.
Source for that help MSDN Post
Refining Hans answer, you can derive the WebBrowser for accessing the COM without adding the reference. It is by using the unpublished Winforms WebBrowser.AttachInterface and DetachInterface methods.
More elaborated here.
Here is the code:
Usage (change your WebBrowser instance to WebBrowserNewWindow2)
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.webBrowser1.NewWindow2 += webBrowser_NewWindow2;
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
webBrowser1.NewWindow2 -= webBrowser_NewWindow2;
base.OnFormClosing(e);
}
void webBrowser_NewWindow2(object sender, WebBrowserNewWindow2EventArgs e)
{
var popup = new Form1();
popup.Show(this);
e.PpDisp = popup.Browser.ActiveXInstance;
}
public WebBrowserNewWindow2 Browser
{
get { return webBrowser1; }
}
}
Code:
using System;
using System.Security.Permissions;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace SHDocVw
{
public delegate void WebBrowserNewWindow2EventHandler(object sender, WebBrowserNewWindow2EventArgs e);
public class WebBrowserNewWindow2EventArgs : EventArgs
{
public WebBrowserNewWindow2EventArgs(object ppDisp, bool cancel)
{
PpDisp = ppDisp;
Cancel = cancel;
}
public object PpDisp { get; set; }
public bool Cancel { get; set; }
}
public class WebBrowserNewWindow2 : WebBrowser
{
private AxHost.ConnectionPointCookie _cookie;
private WebBrowser2EventHelper _helper;
[PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
protected override void CreateSink()
{
base.CreateSink();
_helper = new WebBrowser2EventHelper(this);
_cookie = new AxHost.ConnectionPointCookie(
this.ActiveXInstance, _helper, typeof(DWebBrowserEvents2));
}
[PermissionSetAttribute(SecurityAction.LinkDemand, Name = "FullTrust")]
protected override void DetachSink()
{
if (_cookie != null)
{
_cookie.Disconnect();
_cookie = null;
}
base.DetachSink();
}
public event WebBrowserNewWindow2EventHandler NewWindow2;
private class WebBrowser2EventHelper : StandardOleMarshalObject, DWebBrowserEvents2
{
private readonly WebBrowserNewWindow2 _parent;
public WebBrowser2EventHelper(WebBrowserNewWindow2 parent)
{
_parent = parent;
}
public void NewWindow2(ref object pDisp, ref bool cancel)
{
WebBrowserNewWindow2EventArgs arg = new WebBrowserNewWindow2EventArgs(pDisp, cancel);
_parent.NewWindow2(this, arg);
if (pDisp != arg.PpDisp)
pDisp = arg.PpDisp;
if (cancel != arg.Cancel)
cancel = arg.Cancel;
}
}
[ComImport, Guid("34A715A0-6587-11D0-924A-0020AFC7AC4D"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch),
TypeLibType(TypeLibTypeFlags.FHidden)]
public interface DWebBrowserEvents2
{
[DispId(0xfb)]
void NewWindow2(
[In, Out, MarshalAs(UnmanagedType.IDispatch)] ref object ppDisp,
[In, Out] ref bool cancel);
}
}
}
I know the question is very old but I solved it this way: add new reference, in COM choose Microsoft Internet Controls and in the code, before the click that opens a new window add the following:
SHDocVw.WebBrowser_V1 axBrowser = (SHDocVw.WebBrowser_V1)webBrowser1.ActiveXInstance;
axBrowser.NewWindow += axBrowser_NewWindow;
and then add the following method:
void axBrowser_NewWindow(string URL, int Flags, string TargetFrameName, ref object PostData, string Headers, ref bool Processed)
{
Processed = true;
webBrowser1.Navigate(URL);
}

Wpf child form, OnClosing event and await

I have a child form launched form a parent form with:
ConfigForm cfg = new ConfigForm();
cfg.ShowDialog();
This child form is used to configure some application parameters.
I want to check if there are some changes not saved, and if so, warn the user.
So my On OnClosing event is declared this way:
private async void ChildFormClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
// Here i call a function that compare the current config with the saved config
bool isUptated = CheckUnsavedChanges();
// If updated is false, it means that there are unsaved changes...
if (!isUpdated)
{
e.Cancel = true;
// At this point i create a MessageDialog (Mahapps) to warn the user about unsaved changes...
MessageDialogStyle style = MessageDialogStyle.AffirmativeAndNegative;
var metroDialogSettings = new MetroDialogSettings()
{
AffirmativeButtonText = "Close",
NegativeButtonText = "Cancel"
};
var result = await this.ShowMessageAsync("Config", "There are unsaved changes, do you want to exit?", style, metroDialogSettings);
// If we press Close, we want to close child form and go back to parent...
if (result == MessageDialogResult.Affirmative)
{
e.Cancel = false;
}
}
}
My logic says that if i declare e.cancel to false it will continue closing the form, but it doesn't happen, the child form remains open.
My guess is that the async call is doing something i don't understand, because if i declare ChildFormClosing in this way:
private async void ChildFormClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
bool isUptated = CheckUnsavedChanges();
e.Cancel = true;
if (!isUpdated)
{
MessageDialogStyle style = MessageDialogStyle.AffirmativeAndNegative;
var metroDialogSettings = new MetroDialogSettings()
{
AffirmativeButtonText = "Close",
NegativeButtonText = "Cancel"
};
var result = await this.ShowMessageAsync("Config", "There are unsaved changes, do you want to exit?", style, metroDialogSettings);
if (result == MessageDialogResult.Affirmative)
{
e.Cancel = false;
}
}
else
{
e.Cancel = false;
}
}
The final else e.Cancel = false works and the child form is closed...
Any clue?
Thanks!
Since this method is an event handler for a window, it will be called on the UI thread already, so there is no need to show the message box asynchronously.
As for the strange behavior that you are seeing, this is related to the await in the event handler. When you await a method call, what is actually happening is that everything up until the await is executed as normal, but once the await statement is reach control returns to the caller. Once the method that is awaited upon returns, then the rest of the original method executes.
The code that fires the OnClosing event is probably not designed with asynchronous event handlers in mind, so it assumes that if an event handler returns, it has finished whatever work it needs to do. Since your event handler sets CancelEventArgs.Cancel to true before it awaits on a method call, the caller to your event handler sees that it is set to true, so it doesn't close the form.
This is why showing the message box synchronously works: the entire method is executed before control returns to the caller, so CancelEventArgs.Cancel is always set to its expected value.
Raymond Chen recently posted two articles about async that might be interesting reading: Crash course in async and await and The perils of async void. The second article describes why async event handlers tend to not work how you expect them to.
The main problem with using async/await in OnClosing is, as Andy explained, that as soon as the await statement is executed, control is returned to the caller and the closing process continues.
We can work around this by making another round trip back to OnClosing after awaiting, this time with a flag to indicate whether to actually close or not, but the problem is that calling Close while the Window is already closing, is not allowed and throws an exception.
The way to solve this issue is to simply defer the execution of Close to after the current closing process, at which point it becomes valid again to close the window.
I wanted to do something like this to allow the user to handle async closing logic in the ViewModel.
I don't know if there are other edge cases that I haven't covered, but this code so far works for me:
CoreWindow.cs
public class CoreWindow : Window
{
private bool _isClosing;
private bool _canClose;
private BaseDialogViewModel ViewModel => (BaseDialogViewModel) DataContext;
public CoreWindow()
{
DataContextChanged += OnDataContextChanged;
}
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue is BaseDialogViewModel oldDataContext)
{
oldDataContext.Closed -= OnViewModelClosed;
}
if (e.NewValue is BaseDialogViewModel newDataContext)
{
newDataContext.Closed += OnViewModelClosed;
}
}
private void OnViewModelClosed(object sender, EventArgs e)
{
if (!_isClosing)
{
_isClosing = true;
Close();
}
}
protected override async void OnClosing(CancelEventArgs e)
{
if (ViewModel == null)
{
base.OnClosing(e);
return;
}
if (!_canClose)
{
// Immediately cancel closing, because the decision
// to cancel is made in the ViewModel and not here
e.Cancel = true;
base.OnClosing(e);
try
{
// Ask ViewModel if allowed to close
bool closed = await ViewModel.OnClosing();
if (closed)
{
// Set _canClose to true, so that when we call Close again
// and return to this method, we proceed to close as usual
_canClose = true;
// Close cannot be called while Window is in closing state, so use
// InvokeAsync to defer execution of Close after OnClosing returns
_ = Dispatcher.InvokeAsync(Close, DispatcherPriority.Normal);
}
}
catch (Exception ex)
{
// TODO: Log exception
}
finally
{
_isClosing = false;
}
}
base.OnClosing(e);
}
}
BaseDialogViewModel.cs
public class BaseDialogViewModel : BaseViewModel
{
public event EventHandler Closed;
public bool? DialogResult { get; set; }
public void Close()
{
Closed?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Override to add custom logic while dialog is closing
/// </summary>
/// <returns>True if should close dialog, otherwise false</returns>
public virtual Task<bool> OnClosing()
{
return Task.FromResult(true);
}
}
BaseViewModel just contains some validation and property notification stuff, not really relevant to show here.
Big thanks to Rick Strahl for the Dispatcher solution!
UPDATE:
It's possible to use await Task.Yield(); instead of Dispatcher.InvokeAsync.

InvokeCommand from Observable.Timer causes cross-thread issue

I have a ReactiveCommand that refreshes data and is bound to a Button in XAML. The functionality works fine, but I also want to execute the command on a timer.
I have the following code - SetupAutoRefresh is called from the ctor in my VM, but when the Observable fires, I get an exception with the message: "The calling thread cannot access this object because a different thread owns it."
VM:
private void SetupAutoRefresh() {
Observable.Timer(TimeSpan.FromSeconds(5))
.Select(_ => Unit.Default)
.ObserveOn(RxApp.MainThreadScheduler)
.InvokeCommand(RefreshData);
RefreshData = ReactiveCommand.CreateFromTask(Refresh);
}
private async Task Refresh()
{
var updatedData = await _repository.GetAll();
Data.Merge(updatedData);
}
private ReactiveCommand<Unit, Unit> _refreshData;
public ReactiveCommand<Unit, Unit> RefreshData
{
get { return _refreshData; }
set { this.RaiseAndSetIfChanged(ref _refreshData, value); }
}
private IReactiveList<Model> _data;
public IReactiveList<Model> Data
{
get { return _data; }
set { this.RaiseAndSetIfChanged(ref _data, value); }
}
XAML:
<Button Grid.Column="2"
Command="{Binding RefreshData}"
Style="{StaticResource ToolbarButtonTheme}"
Content="{StaticResource RefreshToolbarIcon}"
ToolTip="Refresh Search"/>
Debug output provides this stacktrace:
at System.Windows.Threading.Dispatcher.VerifyAccess()
at System.Windows.DependencyObject.GetValue(DependencyProperty dp)
at System.Windows.Controls.Primitives.ButtonBase.get_Command()
at System.Windows.Controls.Primitives.ButtonBase.UpdateCanExecute()
at System.Windows.Controls.Primitives.ButtonBase.OnCanExecuteChanged(Object >sender, EventArgs e)
at
System.Windows.Input.CanExecuteChangedEventManager.HandlerSink.OnCanExecuteChanged(Object sender, EventArgs e)
at ReactiveUI.ReactiveCommand.OnCanExecuteChanged() in C:\projects\reactiveui\src\ReactiveUI\ReactiveCommand.cs:line 628
I've tried many different variations of attempting to schedule this on the RxApp.MainThreadScheduler but without any joy - ObserveOn, SubscribeOn, setting the output scheduler... none of which I had much hope for anyway.
Feel like I'm missing something obvious here, but have been banging my head against a brick wall for the whole afternoon. Surely this scenario is possible in RxUI?
The Refresh method runs on a background thread; you can't modify databound properties within that method.
Try this:
private void SetupAutoRefresh() {
Observable.Timer(TimeSpan.FromSeconds(5))
.Select(_ => Unit.Default)
// remove ObserveOn here; the Command will run on the background
.InvokeCommand(RefreshData);
RefreshData = ReactiveCommand.CreateFromTask(Refresh);
// RefreshData.Subscribe is guaranteed to run on the UI thread
RefreshData.Subscribe(listOfModels => Data.Merge(listOfModels))
}
private async Task Refresh()
{
// all this method does is deliver a list of models
return await _repository.GetAll();
}
// return IEnumerable<Model> from the command
public ReactiveCommand<Unit, IEnumerable<Model>> RefreshData
Now, your ReactiveCommand simply fetches the new data, and returns it to you on the UI thread within Subscribe :)
Figured out the issue - looks like the Observable needed to be created on the UI thread. I missed it from the original post, but the SetupAutoRefresh method had been called from another async method, which had switched context during a prior await.

asynchronous UI update from ViewModel in WPF

I am having a problem with getting data from db and showing in UI asynchronously.
I am using MVVM light, when I click the button, action is triggered in ViewModel:
private void SearchQuery(string query)
{
_redisModel.GetFriendsListAsync(query);
}
At some point GetFriendsListCompleted is called by background thread notifing viewmodel that job is done.
At this point I need to update ListBox ItemSource. But when I try to update is I get
“The calling thread cannot access this object because a different thread owns it”
I have tried Dispatcher.CurrentDispatcher.Invoke(),App.Current.Dispatcher.Invoke() and different magic, but it still doesn’t work.
I tried to give UI dispatcher to ViewModel and then call it from there - didn't work.
private string filterText = string.Empty;
public string FilterText
{
get { return filterText; }
set
{
filterText = value;
this.RaisePropertyChanged(() => this.FilterText);
this.FriendsList.View.Refresh(); // Here where exception is happening.
}
}
I tried to change this line to
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
() =>this.FriendsList.View.Refresh())); - still the same.
I am using Telerik ListBox to display items. FriendList is CollectionViewSource(http://www.telerik.com/help/wpf/radlistbox-overview.html). It works when I use Telerik example from WPF Control Examples. Problems start to occur when I use my async methods.
Type of view is System.ComponentModel.ICollectionView it is used for Filtering and Grouping.
I have also tried to just assign ObservableCollection to Items property of the ListBox and it doesn't work either.
A bit more details on how _redisModel.GetFriendsListAsync works:
In the end(after all chain of calls) it ends up here:
public GetAsyncResult(Func<T> workToBeDone, Action<IAsyncResult> cbMethod, Object state)
{
_cbMethod = cbMethod;
_state = state;
QueueWorkOnThreadPool(workToBeDone);
}
ThreadPool.QueueUserWorkItem(state =>
{
try
{
_result = workToBeDone();
}
catch (Exception ex)
{
_exception = ex;
}
finally
{
UpdateStatusToComplete(); //1 and 2
NotifyCallbackWhenAvailable(); //3 callback invocation
}
});
In viewmodel I have method:
private void GetFriendsListCompleted(object sender, ResultsArgs<Friend> e)
{
if (!e.HasError)
{
var curr = e.Results;
if (curr != null)
{
this.FriendsList= new CollectionViewSource();
this.FriendsList.Source = list;
this.FriendsList.Filter += this.FriendFilter;
FilterText = "";
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
() => this.FriendsList.View.Refresh()));
}
}
Can anybody please help me with this ?
Thank you
You are creating CollectionViewSource in one thread and refreshing that in another thread (dispatcher thread). Update your GetFriendsListCompleted to
private void GetFriendsListCompleted(object sender, ResultsArgs<Friend> e)
{
if (!e.HasError)
{
var curr = e.Results;
if (curr != null)
{
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
() => {
this.FriendsList= new CollectionViewSource();
this.FriendsList.Source = list;
this.FriendsList.Filter += this.FriendFilter;
FilterText = "";
this.FriendsList.View.Refresh();
}));
}
}
}
You haven't shown any of the code that's actually running on the background thread on completion but I'm guessing that in it you're creating a collection object that you're then trying to assign to your CollectionView. When the CV tries to update (on the UI thread) from your Refresh call it would then try to use the collection that's owned by the other thread.
If you include the relevant code it would be easier to say for sure.

Timer not getting called when backgroundworker running

I have a WPF window with a button that spawns a BackgroundWorker thread to create and send an email. While this BackgroundWorker is running, I want to display a user control that displays some message followed by an animated "...". That animation is run by a timer inside the user control.
Even though my mail sending code is on a BackgroundWorker, the timer in the user control never gets called (well, it does but only when the Backgroundworker is finished, which kinda defeats the purpose...).
Relevant code in the WPF window:
private void button_Send_Click(object sender, RoutedEventArgs e)
{
busyLabel.Show(); // this should start the animation timer inside the user control
BackgroundWorker worker = new BackgroundWorker();
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerAsync();
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
this.Dispatcher.Invoke((Action)(() =>
{
string body = textBox_Details.Text;
body += "User-added addtional information:" + textBox_AdditionalInfo.Text;
var smtp = new SmtpClient
{
...
};
using (var message = new MailMessage(fromAddress, toAddress)
{
Subject = subject,
Body = body
})
{
smtp.Send(message);
}
}));
}
Relevant code in the user control ("BusyLabel"):
public void Show()
{
tb_Message.Text = Message;
mTimer = new System.Timers.Timer();
mTimer.Interval = Interval;
mTimer.Elapsed += new ElapsedEventHandler(mTimer_Elapsed);
mTimer.Start();
}
void mTimer_Elapsed(object sender, ElapsedEventArgs e)
{
this.Dispatcher.Invoke((Action)(() =>
{
int numPeriods = tb_Message.Text.Count(f => f == '.');
if (numPeriods >= NumPeriods)
{
tb_Message.Text = Message;
}
else
{
tb_Message.Text += '.';
}
}));
}
public void Hide()
{
mTimer.Stop();
}
Any ideas why it's locking up?
Using Dispatcher.Invoke in your worker_DoWork method is putting execution back on the UI thread, so you are not really doing the work asynchronously.
You should be able to just remove that, based on the code you are showing.
If there are result values that you need to show after the work is complete, put it in the DoWorkEventArgs and you will be able to access it (on the UI thread) in the worker_RunWorkerCompleted handler's event args.
A primary reason for using BackgroundWorker is that the marshalling is handled under the covers, so you shouldn't have to use Dispatcher.Invoke.

Resources