I have a WPF application with a few dozen user panels displayed in a listbox, each panel representing a task to execute. Within each panel there will be a ‘Start’ and ‘Stop’ button for controlling a thread.
Each panel includes a private background worker for execution and only one can be executing at a time since they access a shared resource. Therefore I want to disable the buttons in every panel that isn’t running a task once a task begins in any panel and, of course, re-enable them when everything completes.
So I want to enable & disable based on 2 properties:
1. Whether the private background worker instance variable is null
2. Whether a public static object has a lock (obtained using Monitor.Enter or lock)
I would like to enable/disable the buttons based on the following logic:
‘Start’ button:
– enabled if public object is not locked (means no threads are running), else disabled (at least one thread, possibly the one from this class, is running)
‘Stop’ button
– Enabled if private background worker is not null (thread from this class is starting/running), else disabled (no applicable threads to stop)
When a thread starts it will obtain a lock on the shared object and initialize the local background worker which will enable the single stop button and disable all other start buttons.
I’m pretty new to WPF and am looking into data binding. I could probably figure out how to bind to the background worker == or != null but I’m not sure about how to test whether a lock exists on an object and how to bind to that.
Examples:
Here is some sample code following up on answers provided below
Create a userpanel with two buttons (no bindings implemented for stop button)
<StackPanel Orientation="Horizontal">
<Button Margin="2" x:Name="btnStart" Content="Start" Click="btnStart_Click" IsEnabled="{Binding CanCommandsExecute}"/>
<Button Margin="2" x:Name="btnStop" Content="Stop"/>
</StackPanel>
Place multiple instances of this into a window
<StackPanel Orientation="Vertical">
<wpfsample:TestControl/>
<wpfsample:TestControl/>
<wpfsample:TestControl/>
</StackPanel>
And here is the codebehind for TestControl
public partial class TestControl : UserControl, INotifyPropertyChanged
{
private static bool IsLocked = false;
private static object threadlock = new object();
private BackgroundWorker _worker;
public event PropertyChangedEventHandler PropertyChanged;
private bool _canCommandsExecute = true;
public bool CanCommandsExecute {
get { return _canCommandsExecute && (!IsLocked); }
set { _canCommandsExecute = value; OnPropertyChanged("CanCommandsExecute"); } }
public TestControl()
{
DataContext = this;
InitializeComponent();
}
private void btnStart_Click(object sender, RoutedEventArgs e)
{
Monitor.Enter(threadlock);
try
{
IsLocked = true;
this.CanCommandsExecute = false;
_worker = new BackgroundWorker();
_worker.DoWork += (x, y) => { Thread.Sleep(5000); };
_worker.RunWorkerCompleted += WorkComplete;
_worker.RunWorkerAsync();
}
catch { Monitor.Exit(threadlock); }
}
private void WorkComplete(object sender, EventArgs e)
{
IsLocked = false;
this.CanCommandsExecute = true;
Monitor.Exit(threadlock);
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
This partially solves the question. When you hit start it disables the button and runs a background task. It does so using a WPF binding as requested as well.
The outstanding question is how to make ALL the start buttons disable instead of just one. I’m getting a lock on a static object (which isn’t working right at the moment, looking into that)
Hopefully this example helps
Without knowing exactly what your binding structure is (code behind, view models, etc), I would suggest that you forget about figuring out how to make the GUI/WPF understand your underlying object model and focus on making your code easy to use from the XAML.
To wit, don’t spend time figuring out how to bind XAML to whether or not something is null and whether or not something else is locked. Instead, expose a property from your binding target that resolves these into what the object wants.
Rachel’s RelayCommand (or perhaps a DelegateCommand) is a good idea, as that is a nice way to work with buttons. However, if you’re new to WPF, that might be a little much to start with, in terms of really understanding what’s going on.
Let’s say that your buttons bind to some click event that is handled in your code behind:
Now, if you make this piece of code behind the source of your binding:
Your buttons in XAML then bind to the boolean property here for their IsEnabled properties, which will be set to false when you start a task and then set back to true when the task completes. The property setter will trigger PropertyChanged for the GUI, which will update the buttons back to enabled.
To be clear, this is something that is conceptually easy to understand if you’re new to the framework, but not the best way to do it, in my opinion. It’s a stepping stone to the best way to do it. Once you understand what’s going on here, you can look into using View Models for binding and into looking at binding buttons to RelayCommands or DelegateCommands on the view model, instead of using event handlers and button IsEnabled. That was my experience, anyway, when learning WPF. ViewModels/Commands are elegant, but it’s easier to understand the benefits of them and why they’re often preferable once you’ve done it the easier-to-understand, code-behind way first.