I have a WPF dll that contains a number of Window classes and exposes methods that display those windows.
I also have a separate WinForms project that is calling one of those methods in the WPF project inside the DoWork method of a BackgroundWorker component.
On the line of code that instantiates a WPF Window, I get the following runtime error:
The calling thread must be STA, because many UI components require this.
A google search let me to this discussion. (Turns out Jon Skeet answers questions on other sites in addition to Stack Overflow!) He linked to this article, which states
The BackgroundWorker component works well with WPF ...
That article also mentions using the DispatcherObject class, but I don't understand how to make that work and I would rather just continue using my BackgroundWorker component.
As a test case, I came up with the following code to reproduce the error. In the WPF class library, here is the code in Window1.xaml.vb
Partial Public Class Window1
Public Shared Function ShowMe() As Boolean?
Dim w = New Window1 'Error appears on this line.
Return w.ShowDialog()
End Function
End Class
In the WinForms application, here is the code in Form1.vb
Imports System.ComponentModel
Public Class Form1
Private WithEvents worker As BackgroundWorker
Private Sub doWord(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles worker.DoWork
WpfLibrary.Window1.ShowMe()
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
worker = New BackgroundWorker
worker.RunWorkerAsync()
End Sub
End Class
Even when the BackgroundWorker component is placed in Window1.xaml.vb itself, the same error occurs. So, is that article wrong and I can't really use a BackgroundWorker with WPF? Or is there something else I need to do to get it to work?
If the BackgroundWorker won't work, then how would I replace the code in Form1.vb above to use a Dispatcher instead?
You can use a background worker with WPF, that's not your problem.
Your problem is that you can't perform any task that updates the UI from outside of the main UI thread in either a winform or WPF application and the BackgroundWorker's DoWork method is running in a different thread.
Therefore you must open the new window outside of the BackgroundWorker's background thread, either before you start the BackgroundWorker, or in its RunWorkerCompleted event.
Without knowing the code that surrounds the call to open the window it is difficult for me to advise further, but I hope that points you in the right direction.
Your worker could also create its own thread, mark that thread as STA, and then call Thread.Join() to wait for it to terminate. You would then be able to create and show the window on your new thread (although note that no other thread will be able to interact with it without using the Dispatcher).
Example in C#:
Thread workerThread = new Thread(ShowMyWindow);
workerThread.SetApartmentState(ApartmentState.STA);
workerThread.Start();
workerThread.Join();
And then:
private void ShowMyWindow()
{
WpfLibrary.Window1.ShowMe()
}
Related
I have a wpf Custom Control which is now raising custom events. In a test project that I have created to test the ongoing development of the control I have placed a copy of my new custom control on the main window.
In the properties dialog for said control I can see my new event and I can type in the name for a handler for it in the code behind the main window and it's not only created for me but when I place a break point on it I see both the sender and my own event args raised in the control.
What I would now like to know Is what is the correct way to alert a viewmodel about that event respecting the principles of MVVM.
Below is the xaml markup for my custom control in the mainwindow of the test application
<WpfControls:VtlDataNavigator Name="vtlDn"
ItemsSource="{Binding Path=Orders}"
Grid.Row="1"
AddButtonVisibility="True"
CancelNewRecordButtonVisibility="True"
SaveButtonVisibility="True" />
The event I want to catch is called 'RecordControlButtonClicked' and this is the event automatically created in the mainwindow code behind
Private Sub MyCustomHandlerForControlEvent(sender As Object, e As ViewToLearn.WpfControls.VtlDataNavigatorEventArgs) Handles vtlDn.RecordControlButtonClicked
End Sub
So now what I'm after is the correct syntax in both the xaml and my new MainWindowViewModel to have this event transferred across to the viewmodel for handling (or suggestions as to the most efficient way to do this idf there is no direct route.
FYI I already have a relay command set up in the test application but the way that I've used it to bind commands in the past doesn't appear to be working with events.
Edit
One approach I've tried which appears to work is to add the following in the code behind of the main window's loaded event.
Class MainWindow
Public Sub New()
InitializeComponent
DataContext = New MainWindowViewModel
End Sub
Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
Dim mvm As MainWindowViewModel = DirectCast(DataContext, MainWindowViewModel)
mvm.vtldn = vtlDn
End Sub
End Class
and then add the following in the viewmodel;
Private _vtldn As ViewToLearn.WpfControls.VtlDataNavigator
Public Property vtldn As ViewToLearn.WpfControls.VtlDataNavigator
Get
Return _vtldn
End Get
Set(ByVal Value As ViewToLearn.WpfControls.VtlDataNavigator)
If (_vtldn Is Value) Then Return
If Not IsNothing(vtldn) Then
RemoveHandler vtldn.RecordControlButtonClicked, AddressOf MyCustomHandler
End If
_vtldn = Value
If Not IsNothing(vtldn) Then
AddHandler vtldn.RecordControlButtonClicked, AddressOf MyCustomHandler
End If
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(NameOf(vtldn)))
End Set
End Property
Private Sub MyCustomHandler(ByVal sender As Object, ByVal e As VtlDataNavigatorEventArgs)
'this does get called when buttons in the custom control are clicked
End Sub
However I'm acutely aware that this is not really 'pure' mvvm so if there are better ways to do this I'm open to suggestions.
I have a very basic question on vb.NET, so basic that I didn't find any answer elsewhere.
I've written with VB express a code that is a simple form proposing various choices through checkboxes.
These choices have to be registered in an array, which I convert later into a textfile to be Perl-processed.
I'm searching a way to zeroise this big array with loops before use, but in fact I don't know how to execute instructions which wouldn't be triggered by events in my main form.
The frame looks like that :
Public Class Form1
'Variables declaration...
'Several boxes like that :
Private Sub CheckBox1_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles CheckBox1.CheckedChanged
'Instructions...
End Sub
End Class
To launch this application, I simply click on the associated .exe file.
Basically, my question is :
is there a way to execute instructions that wouldn't be launched by an user event, but immediatly launched when the main Form1 is shown ?
Sorry for being retarded, and thank you in advance if you can help me.
The Form.Load event for instance gets launched as soon as the form is about to be shown :)
The Form will raise a Shown event when it is shown simply handle that:
Private Sub Form_Shown(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Me.Shown
'Instructions...
End Sub
It can be better to do initialisation in the constructor right after the Winforms designer adds the comment: 'Add any initialization after the InitializeComponent() call
I am working on a WPF .NET 3.5 application that does a few long tasks that I would like to make a seperate thread to the UI thread to process the data and then when completed update some labels in the UI. The problem I am having is that the function I have uses two parameters and I am struggling to work out how to call a function with multiple parameters in a thread and update the UI.
I have been playing around with using a Delegate Sub to call the function (it is located in a seperate Class), and my code was also attempting to return a dataset from the function for the calling thread to update the UI, but I am not sure if this is the best practice to achieve this or wether I should use a dispatcher for the called function to do the UI updating (feedback would be greatly appreciated).
My code is as follows.
Private Delegate Sub WorkHandler(ByVal input1 As String, ByVal input2 As String)
Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
Dim test_helper As New test_global
Dim worker As New WorkHandler(AddressOf test_helper.getWeatherData)
worker.BeginInvoke("IDA00005.dat", "Adelaide", AddressOf weatherCallBack, Nothing)
' The following is what I was using prior to attempting to work with threads, do I continue to update the UI here getting the called function to return a dataset, or do I have the called function do the UI updating?
'Dim ls As DataSet = test_helper.getWeatherData("IDA00005.dat", "Adelaide")
'Dim f_date As String = ls.Tables("weather").Rows(1).Item(3).ToString
End Sub
Public Sub weatherCallBack(ByVal ia As IAsyncResult)
CType(CType(ia, Runtime.Remoting.Messaging.AsyncResult).AsyncDelegate, WorkHandler).EndInvoke(ia)
End Sub
And my function that I am attempting to call is as follows:
Class test_global
Public Sub getWeatherData(ByVal filename As String, ByVal location As String) 'As DataSet
...
End Sub
End Class
My problem is if I was to have the calling thread to update the UI, how do I have the called thread to return a dataset, or if the called thread is to update the UI, how do I go about achieving this?
Update:
Following the recomendations provided, I have impletemented a BackgroundWorker that raises a DoWork and RunWorkerCompleted events to get the data and update the UI, respectively. My updated code is as follows:
Class Weather_test
Implements INotifyPropertyChanged
Private WithEvents worker As System.ComponentModel.BackgroundWorker
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(ByVal info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub
Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
Dim test_helper As New test_global
Dim worker = New System.ComponentModel.BackgroundWorker
worker.WorkerReportsProgress = True
worker.WorkerSupportsCancellation = True
Dim str() = New String() {"IDA00005.dat", "Adelaide"}
Try
worker.RunWorkerAsync(str)
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub
Private Sub worker_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles worker.DoWork
Dim form_Helpder As New test_global
Dim ds As DataSet = form_Helpder.getWeatherData(e.Argument(0), e.Argument(1))
e.Result = ds
End Sub
Private Sub worker_Completed(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) Handles worker.RunWorkerCompleted
If e.Error IsNot Nothing Then
MsgBox(e.Error.Message)
Else
...
NotifyPropertyChanged("lbl_minToday")
...
End If
End Sub
End Class
I then have in a seperate class my functions that get and process the data.
I am able to debug the code in Visual Studio 2010 and the form displays but the labels are not updating, and when I put a breakpoint at the RunWorkerAsync line the line is called and the Window_Loaded sub completes but it appears that none of the DoWork or RunWorkerCompleted events are called (well at least the functions are not).
Can anyone provide some assistance on how I can debug the code to see why these functions are not being called?
Also, is the above code the correct method that was recommended in the answers?
Any assistance provided will be greatly appreciated.
Matt
You should use the BackgroundWorker component.
You should call your function in the DoWork handler and set e.Result to the returned DataSet.
You can then update the UI in the RunWorkerCompleted handler.
Use a BackgroundWorker. Implement your long-running method, and pass the arguments to the method in the DoWorkEventArgs parameters of the DoWork event handler. Do not update the UI, either directly or indirectly (i.e. don't update properties of your view model), in this method.
Use progress reporting to update the UI while the method is running: call ReportProgress in the long-running method, passing any information that needs to appear in the UI in the UserState parameter. In the ProgressChanged event handler, get the state from the ProgressChangedEventArgs and update the UI (by, one hopes, updating the appropriate properties of your view model and raising PropertyChanged).
You'll need to implement a class to contain the user state for progress reporting, since UserState is of type object.
Note that you can also update the UI with the results of the long-running method when it's complete. This is done in a similar fashion to progress reporting: implement a class to contain the results, set the Result property of the DoWorkEventArgs to an instance of this class, and the result will be available in the Result property of the WorkCompletedEventArgs when the RunWorkerCompleted event is raised.
Make sure that you handle any exceptions that the long-running method raises by checking the Error property of the WorkCompletedEventArgs.
I don't have much experience with BackgroundWorker (I have only used it once), but it is definitely a solution to your problem. However, the approach I always use is to start a new Thread (not ThreadPool thread via delegates), which acquires a lock and then updates all of the properties. Provided that your class implements INotifyPropertyChanged, you can then use databinding to have the GUI automatically update any time the property changes. I have had very good results with this approach.
As far as passing a Dispatcher to your thread goes, I believe you can do that as well. However, I would tread lightly because I believe I have run into cases with this where the Dispatcher I think I'm using is no longer associated with the main thread. I have a library that needs to call a method that touches GUI elements (even though the dialog might not be displayed), and I solved this problem by using Dispatcher.Invoke. I was able to guarantee that I was using the Dispatcher associated with the main thread because my application uses MEF to Export it.
If you'd like more details about anything I've posted, please comment and I'll do my best to embellish on the topics.
I have a WPF application with form that has a textbox named "txtStatusWindow". I also have a vb.net class handed to me by a co-worker that needs to be called and executed by the code in my application code-behind. My co-worker insists that I will need to use common .net events to update the textbox on my form.
The separate vb.net class:
Public Class globalclass
Public Event txtStatusWindow(ByVal Text As String)
Public Sub InitializeProgram()
RaiseEvent txtStatusWindow("Updating something.")
System.Threading.Thread.Sleep(2000)
RaiseEvent txtStatusWindow("Updating something else.")
System.Threading.Thread.Sleep(2000)
RaiseEvent txtStatusWindow("Updating something other than else.")
System.Threading.Thread.Sleep(2000)
RaiseEvent txtStatusWindow("Updating something other than the else stuff.")
System.Threading.Thread.Sleep(2000)
End Sub
End Class
I need to be able to call the sub "InitializeProgram()" from my code-behind, and it needs to be able to update "txtStatusWindow.text" as it runs.
I told him that the updating of the text box can be done with data-binding, but I don't know how to integrate a separate class like this into my project, how to call methods in it, or how to cause it to update my text blocks through data binding.
I also suggested that the methods in this class aren't optimal for connecting to the WPF project anyway, but he just wrote it as an example to discover how to connect the two projects.
Eventually, I will need to integrate classes like these that will be running separate threads to update their data from a dynamic source, and cause many controls to update in my application.
So far, the only way we have been able to get this to work from my code-behind is this:
Partial Public Class SplashScreen
Dim NewText as String
Public WithEvents Globals As globalclass = New globalclass
Public Delegate Sub StringDelegate(ByVal Text As String)
Public SplashText As String
Public Sub New()
MyBase.New()
Me.InitializeComponent()
Me.Show()
Globals.InitializeProgram()
End Sub
Public Sub UpdateSplashscreenHandler(ByVal Text As String) Handles Globals.UpdateSplashScreen
StatusWindowText.Text = Text
End Sub
Notwithstanding the fact that the WPF screen "freezes" until the "globalclass InitializeProgram" method completes (txtStatusWindow.Text does not update while sub without using the esoteric "refresh" extension...), I fully believe we are going about this the wrong way.
There are precious few examples out there concerning the integration and then binding to objects in existing code. Thanks for examining our little quandary.
If this status window is in XAML and the status window is a UserControl, then add a StatusText dependency property to the status window. Then, in the XAML you can bind to the value of that property with something like:
<UserControl x:Name="MyStatusWindow" ...>
<TextBlock Text="{Binding Path=StatusText, ElementName=MyStatusWindow}" />
</UserControl>
Then, from your event, just update the value of that StatusText property.
(Is that even close to what you were asking?)
Also, about that freezing: Instead of doing that updating in the constructor of that class, you might want to do it from the Loaded event of that control. It will still be freezing, though, unless you move it to a separate thread. Right now, that's happening on the same thread that the UI message pump is running on. This is the Dispatcher for that UI.
I have a problem with a solution that I'm trying to develope. This is my scenario:
I have a VB6 application and I would call from this application some WPF windows. I've used the Interop Form Library to define a WinForm like a bridge from VB6 to WPF. The Interop WinForm exposes the methods to start and shutdown the wpf application.
To do that, in the WPF Application I've defined an helper like this:
Public Class StartApplicationHelper
Public Shared Property IsReady As Boolean = False
Public Shared Event NotifyEvent As ValueEnterEventHandler
Public Shared Sub Start()
If System.Windows.Application.Current Is Nothing Then
Try
Dim myApp As Application = New Application
myApp.ShutdownMode = ShutdownMode.OnExplicitShutdown
myApp.InitializeComponent()
IsReady= True
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End If
End Sub
Public Shared Sub Shutdown()
If System.Windows.Application.Current IsNot Nothing Then
System.Windows.Application.Current.Shutdown()
IsReady = False
End If
End Sub
Public Shared Sub DispatchEvent(ByVal eve As String)
If IsReady Then EventDispatcherService.DispatchEvent(eve, New EventDispatcherDataChildWin(String.Empty, Nothing, Nothing, False))
End Sub
Public Shared Sub DispatchResult(ByVal res As Object)
RaiseEvent NotifyEvent(Nothing, New ValueEnterEventArgs(res))
End Sub
End Class
the method DispatchEvent manage the execute of specific event like the opening of an application window.
For example, on winform I've wrote this statements:
MyWpfApp.StartApplicationHelper.Start()
Do While MyWpfApp.StartApplicationHelper.IsReady = False
System.Windows.Forms.Application.DoEvents()
Loop
MyWpfApp.StartApplicationHelper.DispatchEvent("OpenWin1")
in this way I can define an InteropFormMethod to open wpf window from VB6 across the Interop WinForm.
This solution seems work but I have a problem with a particular use case where the wpf application are stopped (shutdown) and then restarted (start). This is the displayed error message: "Cannot create more than one System.Windows.Application instance in the same AppDomain".
I'm trying to modify my helper to manage this case but I still have not found a solution. I would clean the AppDomain to restart wpf application.
How can I do? Can you help me?
LukePet, here is another related question: WPF used within a WinForms application, where to put Application resources?
Perhaps it gives you all the help you need.
The easiest option is just to keep the WPF application "running":
When you want to close the WPF app just close all the open WPF window and do any cleanup you want but don't call Shutdown.
Later, when the WPF part is "restarted" just open the main window without reinitializing the application.
Your other option is running the WPF app in another AppDomain and loading and unloading it when starting/stopping, this is more complicated, makes communication between the WPF part and the rest of the app difficult and, in my opinion, not worth it in this case.