Backgroundworker exits after first expression - c#

I have a list view bound to an ObservableCollection. With that I want to mock a chat application with a WPF gui.
To simulate some activity I wanted to use a Background worker who spams a little bit. But the worker always exits his loop after executing the first statment, so my question is: why does he do that and how to fix it?
here is the code so far:
public partial class MainWindow : Window, INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private string pCurrentUsername;
public string currentUsername
{
get { return pCurrentUsername; }
set
{
pCurrentUsername = value;
if (null != this.PropertyChanged)
{
PropertyChanged(this, new PropertyChangedEventArgs("currentUsername"));
}
}
}
ObservableCollection<ChatPost> items = new ObservableCollection<ChatPost>();
BackgroundWorker bgWorker = new BackgroundWorker();
public MainWindow()
{
InitializeComponent();
currentUsername = "Me";
items.Add(new ChatPost("this", "that"));
bgWorker.DoWork += new DoWorkEventHandler(mockBussiness);
bgWorker.RunWorkerAsync();
lvChat.ItemsSource = items;
}
private void mockBusiness(object o, DoWorkEventArgs args)
{
while (!bgWorker.CancellationPending)
{
items.Add(new ChatPost("guy1", "Ey man!"));
items.Add(new ChatPost("guy2", "What man?"));
}
}
private void btSend_Click(object sender, RoutedEventArgs e)
{
items.Add(new ChatPost(currentUsername, tbMessage.Text));
}
}
public class ChatPost
{
public ChatPost()
{ }
public ChatPost(string username, string message)
{
this.username = username;
this.message = message;
}
public string username { get; set; }
public string message { get; set; }
}
So the only thing that gets executed (meaning printed) is one time "Ey man!"

Yes, you're modifying the UI (indirectly, through the ObservableCollection<>) on a non-UI thread. You're not allowed to do that. I suspect you should find an exception being thrown giving that detail, although it may not be easy to find.
You need to marshal back to the UI thread for any threading operations, in general. If you're using WPF on .NET 4.5, apparently you can using BindingOperations.EnableCollectionSynchronization for this, but I admit I have no direct experience of this.

Related

Something is wrong with Dispatcher. RunAsync () [duplicate]

In a WinUI 3 in Desktop app I have a property to update which is bound to the ui via x:Bind.
I want to use the Dispatcher like I do in WPF to get on the UI thread and avoid the thread error im getting when I update the prop:
System.Runtime.InteropServices.COMException: 'The application called an interface that was marshalled for a different thread. (0x8001010E (RPC_E_WRONG_THREAD))'
Im just not sure how to do it in WinUI 3, when I try
DispatcherQueue.GetForCurrentThread().TryEnqueue(() =>
{
AeParty.OnSyncHub = false; // Prop bound in ui using x:Bind
});
I get this error
DispatcherQueue.GetForCurrentThread() is null
I also tried:
this.DispatcherQueue.TryEnqueue(() =>
{
AeParty.OnSyncHub = false;
});
but it wont compile:
I then found this GitHub issue, so I tried:
SynchronizationContext.Current.Post((o) =>
{
AeParty.OnSyncHub = false;
}, null);
This works but why can't I get onto the UI thread with the Dispatcher in my VM?
DispatcherQueue.GetForCurrentThread() only returns a DispatcherQueue when being called on a thread that actually has a DispatcherQueue. If you call it on a background thread there is indeed no DispatcherQueue to be returned.
So the trick is to call the method on the UI thread and store the return value in a variable that you then use from the background thread, e.g.:
public sealed partial class MainWindow : YourBaseClass
{
public MainWindow()
{
this.InitializeComponent();
}
public ViewModel ViewModel { get; } = new ViewModel();
}
public class ViewModel : INotifyPropertyChanged
{
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
public ViewModel()
{
Task.Run(() =>
{
for (int i = 0; i < 10; i++)
{
string val = i.ToString();
_dispatcherQueue.TryEnqueue(() =>
{
Text = val;
});
Thread.Sleep(2000);
}
});
}
private string _text;
public string Text
{
get { return _text; }
set { _text = value; NotifyPropertyChanged(nameof(Text)); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

The right way to invalidate view model on time

After a bit of research, I haven't found the solution to invalidate and reload view models data on time.
Imagine an application with view model, which is filled with data from the server when initialized, and after some time (let's say, 1 minute), this data gets invalidated and reloaded from the server again.
As I assume, creating dispatcher timer in the view model itself isn't a good idea, because that doesn't feel right to me that view model should be responsible for that kind of things.
In this video author demonstrates creating dispatcher timer in the view model to update some data after a period of time. That's VB.NET, but it looks very similarly to what I'm trying to achieve.
As I see that now, using the dispatcher in the view model:
// ...
public class Foo
{
public string Bar { get; set; }
public int Baz { get; set; }
// ...
}
// ...
public interface IDataService
{
Task<Foo> GetDataAsync();
}
public class FooViewModel : INotifyPropertyChanged
{
private string _bar;
public string Bar
{
get
{
return _bar;
}
set
{
_bar = value;
OnPropertyChanged(nameof(Bar));
}
}
private int _baz;
public int Baz
{
get
{
return _baz;
}
set
{
_baz = value;
OnPropertyChanged(nameof(Baz));
}
}
IDataService service;
DispatcherTimer dispatcherTimer;
private async void dispatcherTimer_Tick(object sender, EventArgs eventArgs)
{
await InvalidateAndReloadDataAsync();
}
private void InitializeDispatcher()
{
dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
dispatcherTimer.Interval = new TimeSpan(0, 1, 0);
dispatcherTimer.Start();
}
public async Task InvalidateAndReloadDataAsync()
{
Foo foo = await service.GetDataAsync();
Bar = foo.Bar;
Baz = foo.Baz;
}
public FooViewModel(IDataService dataService)
{
service = dataService;
InitializeDispatcher();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName()] string name = null)
{
if (name != null) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
// ...
}
Is it normal to actually use the dispatcher in the view model or there is much better solution?

c# MVVM InvalidOperationException on BackgroundWorker when using Dispatcher

i'm currently facing an issue in C# WPF. I wrote an application, that generates long running reports in a background task. I am using prism with MVVM and trying to run the expensive background task with a Async ICommand implementation and a BackgroundWorker. But when i try to retrieve the resulting report
Report = asyncTask.Result;
i get an InvalidOperationException stating "The calling thread cannot access this object because a different thread owns it.".
Yes, i have already tried to invoke a dispatcher (its the first thing you'll find on google, stackoverflow etc when you search for the exception message). I have tried several variants like for instance:
Dispatcher.CurrentDispatcher.Invoke(() => Report = asyncTaks.Result);
or
Report.Dispatcher.Invoke(() => Report = asyncTask.Result);
but each time i get this exception.
I am suspecting that the way i am calling the report UI is not adequate.
The structure looks in brief as follows:
MainWindowViewModel
-> SubWindowCommand
SubWindowViewModel
-> GenerateReportCommand
ReportViewModel
-> GenerateReportAsyncCommand
<- Exception on callback
I am out of ideas, does anybody have a clue what i might be doing wrong?
Below are a few code fragments
Report Generator View Model:
public class ReportFlowDocumentViewModel : BindableBase
{
private IUnityContainer _container;
private bool _isReportGenerationInProgress;
private FlowDocument _report;
public FlowDocument Report
{
get { return _report; }
set
{
if (object.Equals(_report, value) == false)
{
SetProperty(ref _report, value);
}
}
}
public bool IsReportGenerationInProgress
{
get { return _isReportGenerationInProgress; }
set
{
if (_isReportGenerationInProgress != value)
{
SetProperty(ref _isReportGenerationInProgress, value);
}
}
}
public ReportFlowDocumentView View { get; set; }
public DelegateCommand PrintCommand { get; set; }
public AsyncCommand GenerateReportCommand { get; set; }
public ReportFlowDocumentViewModel(ReportFlowDocumentView view, IUnityContainer c)
{
_container = c;
view.DataContext = this;
View = view;
view.ViewModel = this;
InitializeGenerateReportAsyncCommand();
IsReportGenerationInProgress = false;
}
private void InitializeGenerateReportAsyncCommand()
{
GenerateReportCommand = new CreateReportAsyncCommand(_container);
GenerateReportCommand.RunWorkerStarting += (sender, args) =>
{
IsReportGenerationInProgress = true;
var reportGeneratorService = new ReportGeneratorService();
_container.RegisterInstance<ReportGeneratorService>(reportGeneratorService);
};
GenerateReportCommand.RunWorkerCompleted += (sender, args) =>
{
IsReportGenerationInProgress = false;
var report = GenerateReportCommand.Result as FlowDocument;
var dispatcher = Application.Current.MainWindow.Dispatcher;
try
{
dispatcher.VerifyAccess();
if (Report == null)
{
Report = new FlowDocument();
}
Dispatcher.CurrentDispatcher.Invoke(() =>
{
Report = report;
});
}
catch (InvalidOperationException inex)
{
// here goes my exception
}
};
}
public void TriggerReportGeneration()
{
GenerateReportCommand.Execute(null);
}
}
This is how i start the ReportView Window
var reportViewModel = _container.Resolve<ReportFlowDocumentViewModel>();
View.ReportViewerWindowAction.WindowContent = reportViewModel.View;
reportViewModel.TriggerReportGeneration();
var popupNotification = new Notification()
{
Title = "Report Viewer",
};
ShowReportViewerRequest.Raise(popupNotification);
with
ShowReportViewerRequest = new InteractionRequest<INotification>();
AsyncCommand definition
public abstract class AsyncCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public event EventHandler RunWorkerStarting;
public event RunWorkerCompletedEventHandler RunWorkerCompleted;
public abstract object Result { get; protected set; }
private bool _isExecuting;
public bool IsExecuting
{
get { return _isExecuting; }
private set
{
_isExecuting = value;
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
}
protected abstract void OnExecute(object parameter);
public void Execute(object parameter)
{
try
{
onRunWorkerStarting();
var worker = new BackgroundWorker();
worker.DoWork += ((sender, e) => OnExecute(e.Argument));
worker.RunWorkerCompleted += ((sender, e) => onRunWorkerCompleted(e));
worker.RunWorkerAsync(parameter);
}
catch (Exception ex)
{
onRunWorkerCompleted(new RunWorkerCompletedEventArgs(null, ex, true));
}
}
private void onRunWorkerStarting()
{
IsExecuting = true;
if (RunWorkerStarting != null)
RunWorkerStarting(this, EventArgs.Empty);
}
private void onRunWorkerCompleted(RunWorkerCompletedEventArgs e)
{
IsExecuting = false;
if (RunWorkerCompleted != null)
RunWorkerCompleted(this, e);
}
public virtual bool CanExecute(object parameter)
{
return !IsExecuting;
}
}
CreateReportAsyncCommand:
public class CreateReportAsyncCommand : AsyncCommand
{
private IUnityContainer _container;
public CreateReportAsyncCommand(IUnityContainer container)
{
_container = container;
}
public override object Result { get; protected set; }
protected override void OnExecute(object parameter)
{
var reportGeneratorService = _container.Resolve<ReportGeneratorService>();
Result = reportGeneratorService?.GenerateReport();
}
}
I think i understand my problem now. I cannot use FlowDocument in a BackgroundThread and update it afterwards, right?
So how can i create a FlowDocument within a background thread, or at least generate the document asynchronously?
The FlowDocument i am creating contains a lot of tables and when i run the report generation synchronously, the UI freezes for about 30seconds, which is unacceptable for regular use.
EDIT:
Found the Solution here:
Creating FlowDocument on BackgroundWorker thread
In brief: I create a flow document within my ReportGeneratorService and then i serialize the FlowDocument to string. In my background worker callback i receive the serialized string and deserialize it - both with XamlWriter and XmlReader as shown here
Your Problem is that you create FlowDocument in another thread. Put your data to the non GUI container and use them after bg comes back in UI thread.

Prevent self-callbacks when changing observed collection

Let's suppose I have an observable collection and two clients that want to:
change it,
observe it and react on state change.
Now, if Client1 changes collection state (for example: adds new item), the collection will fire 'CollectionChanged' event. Since both clients are registered for this event notifications, Client1's handling method will be executed.
In order to avoid self-callback on Client1, I unsubscribe from an event, do my action and subscribe again. This is painful - I must remember about suspending Client1's subscription every time Client1 touches the collection and it just seems like a bad smell. Is there a better way (design pattern, external library) that would help me in callbacks management?
Although in my example I mentioned ObservableCollection and CollectionChanged event, I believe my question is more generic and comes down to: "how to exclude an entity that caused event trigger from event callback".
Thanks in advance!
Problem keeps reoccuring in my solution, bumping the question in a hope someone might help out.
I ran into your problem some times ago I didn't find a proper solution except for this one.
The idea is that when you change the collection you also pass an instance of the object changing it.
Then when the Collection fires the event, it also passes the reference.
So all observers may know which instance did the change, and check for equality.
Here is a basic example of this implementation:
class Program
{
private static MyCollection Collection;
private static MyCollectionModifier Modif1;
private static MyCollectionModifier Modif2;
static void Main(string[] args)
{
Collection = new MyCollection();
Modif1 = new MyCollectionModifier("Modifier 1", Collection);
Modif2 = new MyCollectionModifier("Modifier 2", Collection);
Modif1.AddItem("Test1");
Modif2.AddItem("Test2");
Console.ReadKey();
}
}
public class MyCollectionItemAddedEventArgs:EventArgs
{
public Object ChangeSource { get; set;}
public int newIndex {get;set;}
}
public delegate void MyCollectionItemAddedEventHandler(object sender, MyCollectionItemAddedEventArgs e);
public class MyCollection
{
private List<String> _myList;
public String this[int Index]
{
get { return _myList[Index]; }
}
public event MyCollectionItemAddedEventHandler ItemAdded;
public MyCollection()
{
_myList = new List<string>();
}
protected virtual void OnMyCollectionItemAdded(MyCollectionItemAddedEventArgs e)
{
if (ItemAdded != null)
ItemAdded(this, e);
}
public void AddItem(String Item, object ChangeSource = null)
{
_myList.Add(Item);
var e = new MyCollectionItemAddedEventArgs();
e.ChangeSource = ChangeSource;
e.newIndex = _myList.Count;
OnMyCollectionItemAdded(e);
}
}
public class MyCollectionModifier
{
private MyCollection _collection;
public string Name { get; set; }
public MyCollectionModifier(string Name, MyCollection Collection)
{
this.Name = Name;
_collection = Collection;
_collection.ItemAdded += Collection_ItemAdded;
}
public void AddItem(string Item)
{
_collection.AddItem(Item, this);
}
void Collection_ItemAdded(object sender, MyCollectionItemAddedEventArgs e)
{
if (e != null)
{
if (this.Equals(e.ChangeSource))
{
Console.WriteLine("{0} : I changed the collection", Name);
}
else
{
Console.WriteLine("{0} : Somebody else changed the collection", Name);
}
}
}
}
I've encountered this problem before as well.
Best solution I could come up with is to create extension methods that take the handler of the caller and then automate the unsubscribe/subscribe around the called method, that way you don't have to remember to do it each time and it does not end up cluttering your code either
public static void Add<T>(this ObservableCollection<T> self, T itemToAdd, NotifyCollectionChangedEventHandler handler)
{
self.CollectionChanged -= handler;
self.Add(itemToAdd);
self.CollectionChanged += handler;
}
It does take some effort to create the extensions initially but at least you won't forget to resubscribe. Only real extra code is then around invoking the method
public class ObserverClass
{
public ObserverClass()
{
ObservableIntegers.CollectionChanged += ObservableIntegersOnCollectionChanged;
//Add item to collection while preventing self-handling the callback
ObservableIntegers.Add(1, ObservableIntegersOnCollectionChanged);
}
private void ObservableIntegersOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
// Handle collection change
}
public ObservableCollection<int> ObservableIntegers { get; set; }
}

Interface Event between two projects in one solution and Event Handlers

I have separated two projects in my solution because they each require libraries targeting different CPU.
In one of my project, I just have classes that respond to clicks (let's call it ProjectClick 64 bits libraries), the other one is a sort of UI with an MVVM implementation (ProjectUser 32 bits libraries).
The thing I am searching for is a way to let the ProjectUser know that the click has been performed by the ProjectClick, without the Project Click knowing anything else.
What I have tried so far
I have been scattering the web and books to understand a bit more about C#. From what I understood, to communicate, the best way is to create a Interface. I have been looking at this subject for an answer, and have been trying to implement a third project with an interface between the two.
Ok, here goes the code, (this is a purposely simplified code, I hope it is clear enough)
First the Interface (in a console application)
namespace LinkApplication
{
public interface IEvent
{
bool CompareClick { get; set; }
}
}
Then, the project clicking which is a wpf
namespace ProjectClick
public partial class MainWindow : Window, IEvent
{
public MainWindow()
{
try { InitializeComponent(); }
catch (Exception e)
{
Console.WriteLine(e.InnerException);
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
CompareClick = true;
}
private void Button_Leave(object sender, RoutedEventArgs e)
{
CompareClick = false;
}
}
Finally the UI
namespace ProjectUser
{
public partial class MainWindow : Window, IEvent, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.WindowStartupLocation = WindowStartupLocation.CenterScreen; //start the window at the centre of the screen
DataContext = this;
}
public bool CompareClick { get; set; }
public bool ClickCheck
{
get { return CompareClick; }
set
{
if (value != CompareClick)
{
CompareClick = value;
OnPropertyChanged("ClickCheck");
}
}
}
You can see the realted Label here in the Window
<Label Content="{Binding ClickCheck}" HorizontalAlignment="Left" Margin="690,358,0,0" VerticalAlignment="Top"/>
Here, the value always stays at false, and I don't really understand the logic of the changing value. I am still learning, and I have seen several other ideas on the web like a custom EventHandler, but I don't really understand the implementation between two projects not knowing each others. I will be glad if someone could route me towards a possible solution, or a better way to perform.
Edit
I would preferably like to avoid referring the Project Click in the ProjectUser to keep the privileges of different CPU targeting. The other way around is not a problem.
Thank you for your kind answers.
I have been greatly advised and have looked into Inter Process Communication between instances. I have looked into different things but the most satisfying answer of all was on Omegaman's blog (bit thanks to this subject).
So basically, I have tried to avoid localhost information, thinking there would be a more straightforward solution. But since we have not thought of anything better, I think this is what I was looking for.
What I have found
So now, the solution here was to use a WCF service with NamedPipes. By creating a Sender and Receiver actions, the two process ProjectUser and ProjectClick never encounter each other directly. You have instead a pipe controlled by the WCF. You can see more details on the blog on how to communicate, I just adapted (without great change) what he did by changing the passing information.
One thing to note however
The processes cannot both start at the same time, and the receiver must start first to listen to the information coming through. Basically, the sender has to start afterwards.
I created two windows in WPF, and a WCFServiceLibrary. When the button is clicked, there is an incrementation, and it shows the number on the second screen.
A little bit of code
You can see a lot on Omegaman's blog, and I will just post what I have changed.
On the ProjectUser side, supposed to receive, the label is updated as follows
Receiver pipe = new Receiver();
public MainWindow()
{
InitializeComponent();
//this.WindowStartupLocation = WindowStartupLocation.CenterScreen; //start the window at the centre of the screen
DataContext = this;
pipe.Data += new PipeLink.PipeService.DataIsReady(DataBeingRecieved);
if (pipe.ServiceOn() == false)
MessageBox.Show(pipe.error.Message);
label1.Content = "Listening to Pipe: " + pipe.CurrentPipeName + Environment.NewLine;
}
void DataBeingRecieved(int data)
{
Dispatcher.Invoke(new Action(delegate()
{
label1.Content += string.Join(Environment.NewLine, data);
label1.Content += Environment.NewLine;
}));
}
On the ProjectClick side, supposed to send, the button click updates as follows
int i;
public MainWindow()
{
try { InitializeComponent(); }
catch (Exception e)
{
Console.WriteLine(e.InnerException);
}
i = 0;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
int messages;
i++;
Stopwatch stoop = new Stopwatch();
stoop.Start();
messages = i;
try
{
PipeLink.Sender.SendMessage(messages);
stoop.Stop();
Console.WriteLine(stoop.ElapsedMilliseconds + " ms");
}
catch (Exception u)
{
Console.WriteLine(u);
}
}
The important part of the code, is the creation of the pipe itself, using NetNamedPipeBinding. This is where the whole communication will take place
You can see it in the PipeService code :
public class PipeService : IPipeService
{
public static string URI
= "net.pipe://localhost/Pipe";
// This is when we used the HTTP bindings.
// = "http://localhost:8000/Pipe";
#region IPipeService Members
public void PipeIn(int data)
{
if (DataReady != null)
DataReady(data);
}
public delegate void DataIsReady(int hotData);
public DataIsReady DataReady = null;
#endregion
}
What about the speed?
I was afraid simple data may take longer to arrive than on a simple click. I was mistaken : the first number took longer than the others because of the first connection, so about a second. But after that, for clicking about a 100 times, I had a, average of 10 ms (I know it is not significant data, still I thought it was good to test it a couple of times).
I am pushing everything on the GitHub used with Andreas, for anyone who might be interested.
I still do not know if the code is optimized though. Should you have a better solution, I will happily read it.
As others pointed out your concept of interfaces is wrong still. However i get what you're trying to do.
Try this:
namespace LinkApplication
{
public interface IEventReceiver
{
void Receive<T>(T arg) where T : EventArgs;
}
public class SomeUniqueEvent : EventArgs
{
public bool Clicked { get; set; }
public SomeUniqueEvent(bool clicked)
{
Clicked = clicked;
}
}
public static class EventTunnel
{
private static readonly List<IEventReceiver> _receivers = new List<IEventReceiver>();
public static void Publish<T>(T arg) where T : EventArgs
{
foreach (var receiver in _receivers)
{
receiver.Receive(arg);
}
}
public static void Subscribe(IEventReceiver subscriber)
{
_receivers.Add(subscriber);
}
}
}
namespace ProjectClick
{
public partial class MainWindow : Window
{
public MainWindow()
{
try { InitializeComponent(); }
catch (Exception e)
{
Console.WriteLine(e.InnerException);
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
LinkApplication.EventTunnel.Publish(new LinkApplication.SomeUniqueEvent(true));
}
private void Button_Leave(object sender, RoutedEventArgs e)
{
LinkApplication.EventTunnel.Publish(new LinkApplication.SomeUniqueEvent(false));
}
}
}
namespace ProjectUser
{
public partial class MainWindow : Window, LinkApplication.IEventReceiver, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.WindowStartupLocation = WindowStartupLocation.CenterScreen; //start the window at the centre of the screen
DataContext = this;
LinkApplication.EventTunnel.Subscribe(this);
}
public bool CompareClick { get; set; }
public bool ClickCheck
{
get { return CompareClick; }
set
{
if (value != CompareClick)
{
CompareClick = value;
OnPropertyChanged("ClickCheck");
}
}
}
public void Receive<T>(T arg) where T : EventArgs
{
var casted = arg as SomeUniqueEvent;
if (casted != null)
{
ClickCheck = casted.Clicked;
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Here, you misunderstand what an interface is. Every implementation of an interface is a different one. When you click the button, CompareClick property of ProjectClick project's MainWindow changes value. But that doesn't change the ProjectUser project's MainWindow. They are two completely different objects! The best way that I can think of now, is to make the button public. Alternatively, you can create a method in the MainWindow class of the ProjectClick. Use this method to subscribe to the click event. Something like this:
public void SubscribeToClickEvent (EventHandler handler) {
this.Button.Click += handler //whatever your button is called
}
If you want to encapsulate Button, use the method above. If you don't, then just make it public.
And you ask, how can I access an instance of MainWindow to use the method? The only way I can think of is to make MainWindow a singleton.

Categories