C
C#2mo ago
Eple

✅ Help needed: Buttons update enabled state after two clicks.

Hello, I need help to find out why it takes two clicks before the buttons change the enabled state. Please see the attached video and also my code below:
<StackLayout>
<Button Text="Start the service" Command="{Binding StartTheServiceCommand}" />
</StackLayout>

<StackLayout>
<Button Text="Stop the service" Command="{Binding StopTheServiceCommand}" />
</StackLayout>
<StackLayout>
<Button Text="Start the service" Command="{Binding StartTheServiceCommand}" />
</StackLayout>

<StackLayout>
<Button Text="Stop the service" Command="{Binding StopTheServiceCommand}" />
</StackLayout>
public MainPageViewModel(ILogger<MainPageViewModel> logger, WorkerService workerService)
{
_logger = logger;
_workerService = workerService;

StartTheServiceCommand = new Command(
execute: () =>
{
_logger.LogInformation("Starting the service at: {time}", DateTimeOffset.Now);
Task.Run(async () => await _workerService.StartAsync(new()));
RefreshCanExecutes();
},
canExecute: () => !_workerService.IsRunning);

StopTheServiceCommand = new Command(
execute: () =>
{
_logger.LogInformation("Stopping the service at: {time}", DateTimeOffset.Now);
Task.Run(async () => await _workerService.StopAsync(new()));
RefreshCanExecutes();
},
canExecute: () => _workerService.IsRunning);
}

public ICommand StartTheServiceCommand { get; }
public ICommand StopTheServiceCommand { get; }

private void RefreshCanExecutes()
{
(StartTheServiceCommand as Command)!.ChangeCanExecute();
(StopTheServiceCommand as Command)!.ChangeCanExecute();
}
public MainPageViewModel(ILogger<MainPageViewModel> logger, WorkerService workerService)
{
_logger = logger;
_workerService = workerService;

StartTheServiceCommand = new Command(
execute: () =>
{
_logger.LogInformation("Starting the service at: {time}", DateTimeOffset.Now);
Task.Run(async () => await _workerService.StartAsync(new()));
RefreshCanExecutes();
},
canExecute: () => !_workerService.IsRunning);

StopTheServiceCommand = new Command(
execute: () =>
{
_logger.LogInformation("Stopping the service at: {time}", DateTimeOffset.Now);
Task.Run(async () => await _workerService.StopAsync(new()));
RefreshCanExecutes();
},
canExecute: () => _workerService.IsRunning);
}

public ICommand StartTheServiceCommand { get; }
public ICommand StopTheServiceCommand { get; }

private void RefreshCanExecutes()
{
(StartTheServiceCommand as Command)!.ChangeCanExecute();
(StopTheServiceCommand as Command)!.ChangeCanExecute();
}
4 Replies
canton7
canton72mo ago
RefreshCanExecutes(); doesn't wait for the service to actually start. You queue a bit of work onto the thread pool to start the worker at some point in the future, then immediately call RefreshCanExecutes();, which calls _workerService.IsRunning. But the worker isn't running yet You shouldn't be discarding tasks. The fact that you had to throw a Task.Run into the mix is presumably to work around the fact that the compiler was moaning at you for now awaiting the _workerService.StartAsync call? That was your clue 😉
public MainPageViewModel(ILogger<MainPageViewModel> logger, WorkerService workerService)
{
_logger = logger;
_workerService = workerService;

StartTheServiceCommand = new Command(
execute: async () =>
{
_logger.LogInformation("Starting the service at: {time}", DateTimeOffset.Now);
await _workerService.StartAsync(new());
RefreshCanExecutes();
},
canExecute: () => !_workerService.IsRunning);

StopTheServiceCommand = new Command(
execute: async () =>
{
_logger.LogInformation("Stopping the service at: {time}", DateTimeOffset.Now);
await _workerService.StopAsync(new());
RefreshCanExecutes();
},
canExecute: () => _workerService.IsRunning);
}

public ICommand StartTheServiceCommand { get; }
public ICommand StopTheServiceCommand { get; }

private void RefreshCanExecutes()
{
(StartTheServiceCommand as Command)!.ChangeCanExecute();
(StopTheServiceCommand as Command)!.ChangeCanExecute();
}
public MainPageViewModel(ILogger<MainPageViewModel> logger, WorkerService workerService)
{
_logger = logger;
_workerService = workerService;

StartTheServiceCommand = new Command(
execute: async () =>
{
_logger.LogInformation("Starting the service at: {time}", DateTimeOffset.Now);
await _workerService.StartAsync(new());
RefreshCanExecutes();
},
canExecute: () => !_workerService.IsRunning);

StopTheServiceCommand = new Command(
execute: async () =>
{
_logger.LogInformation("Stopping the service at: {time}", DateTimeOffset.Now);
await _workerService.StopAsync(new());
RefreshCanExecutes();
},
canExecute: () => _workerService.IsRunning);
}

public ICommand StartTheServiceCommand { get; }
public ICommand StopTheServiceCommand { get; }

private void RefreshCanExecutes()
{
(StartTheServiceCommand as Command)!.ChangeCanExecute();
(StopTheServiceCommand as Command)!.ChangeCanExecute();
}
Also, if you use as, always check for null. If you don't expect the cast to fail, use a normal cast, not as. The reason is that if you use as but the cast does fail unexpectedly, you'll get an unhelpful NullReferenceException, rather than a helpful InvalidCastException. So:
private void RefreshCanExecutes()
{
((Command)StartTheServiceCommand).ChangeCanExecute();
((Command)StopTheServiceCommand).ChangeCanExecute();
}
private void RefreshCanExecutes()
{
((Command)StartTheServiceCommand).ChangeCanExecute();
((Command)StopTheServiceCommand).ChangeCanExecute();
}
Eple
Eple2mo ago
@canton7 thank you for your responses, they helped.
canton7
canton72mo ago
Great! Has that solved your problem?
Eple
Eple2mo ago
Yes it has.