I'm working on a WPF application which has many user interactions which start with opening a non model window, allowing some interaction with the window and the rest of the application (think previewing changes, etc) and end when the window is closed. There is also some reputative setup and tear down which I want done at the beginning and end of these actions.
So I have a lot of functions like this:
private void DoOperation()
{
// some setup process
RepetativeSetUp();
Window w = BuildWindow();
w.Closing += (o, e) =>
{
// ...Handle result of window...
// some tear down process
RepetativeCleanUp();
}
// Show the window non modal
// PROBLEM!! this function continues immediately after this line!
// and does not wait for the window to close.
// w.ShowDialog is not an option because I do need that non modal behavior
w.Show();
}
I want to move this repetitive setup/teardown process out of each of these functions and off to some other process. My issue being that there isn't really any single function which encapsulates the "Operation". Typically to solve this type of problem I would do something like:
// I know there are a lot of ways to implement this type of pattern
// It is probably not how I would approach this in a larger code base
// but it is about as simple as I can make it
public static void SetUpCleanUp(Action operation)
{
RepetativeSetUp();
operation.Invoke();
RepetativeCleanUp();
}
private void DoOperation_Simplified()
{
Window w = BuildWindow();
w.Closing += (o, e) =>
{
// ...Handle result of window...
// we want CleanUp here
}
w.Show();
// not CleanUp here
}
// Not good:
// CleanUp is called too soon
SetUpCleanUp(DoOperation_Simplified);
So I came up with this:
public static async Task ShowAsync(this Window w)
{
// I'm sure there is a more elegant way to handle the generic here, and actually have
// this task return a meaningful "dialog result", but for now the int generic can be ignored.
TaskCompletionSource<int> tcs = new();
Task<int> t1 = tcs.Task;
w.Closing += (o, e) =>
{
tcs.SetResult(1);
};
w.Show();
await t1;
}
// and now DoOperation becomes this, which is much easier to work with:
private async Task DoOperation_simplified2()
{
Window w = BuildWindow();
await w.ShowAsync();
// Handle result of window...
}
// and now I'm going to gloss over some of the complication that my SetUpCleanUp
// now has to be async and handle awaiting it's action...
// but I got what I wanted, a single function encompassing my operation?
SetUpCleanUp(DoOperation_Simplified2);
My questions are:
- Am I reinventing the wheel here? I can't really find anything online talking about this type of problem but I can't be the first person to struggle with the issue? Is there an easier way to approach this problem?
- This feels like it might to balloon into a real nightmare to maintain. Has anyone gone down this path before? I don't want to trade one (sort of manageable) maintenance issue for another (maybe less manageable) one.