Reputation: 584
I can change the image when I click on the button but when I tried to run a method AFTER the change, the change never occurs.
I have two ways to change the image on click event of the button
First:
<Window.Resources>
<Image x:Key="ImON" Source="InterruptorON.png" Height="315" Width="435" />
<Image x:Key="ImOFF" Source="InterruptorOFF.png" Height="315" Width="435" />
</Window.Resources>
<Grid>
<Button Name="btnEXE" Height="315" Width="435" Click="Button_Click">
<DynamicResource ResourceKey="ImOFF"/>
</Button>
</Grid>
btnEXE.Content = FindResource("ImON");
SECOND:
<Window.Resources>
<ControlTemplate x:Key="ImOFF" TargetType="Button">
<Image Source="InterruptorOFF.png" Height="315" Width="435" />
</ControlTemplate>
<ControlTemplate x:Key="ImON" TargetType="Button">
<Image Source="InterruptorON.png" Height="315" Width="435" />
</ControlTemplate>
</Window.Resources>
<Grid>
<Button Name="btnEXE" Height="315" Width="435" Click="Button_Click" Template="{StaticResource ImOFF}"/>
</Grid>
btnEXE.Template = (ControlTemplate)FindResource("ImON")
I need to use an if()
to validate the 'ON' or 'OFF' state of the button to change the image and run another code
if (btnEXE.Content == FindResource("ImOFF"))
{
btnEXE.Content = FindResource("ImON");
ThingsToDo();
}
else
{
btnEXE.Content = FindResource("ImOFF");
}
The ThingsToDo()
run perfectly but the change of the image occurs until the end of the method.
I need the image change first and then the rest of the code. Any idea?
Upvotes: 0
Views: 931
Reputation: 4566
Even if you're not going to go the full MVVM route, you can still use WPF commands to handle this sort of UI update.
Assuming that ThingsToDo() is a long running task, you should use an async / await construct via an ICommand implementation.
public class RelayCommandAsync : ObservableObject, ICommand
{
private readonly Func<Task> _execute;
private readonly Func<bool> _canExecute;
public RelayCommandAsync(Func<Task> execute) : this(execute, () => true)
{
}
public RelayCommandAsync(Func<Task> execute, Func<bool> canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
private bool _isExecuting;
public bool IsExecuting
{
get => _isExecuting;
set
{
if (Set(nameof(IsExecuting), ref _isExecuting, value))
RaiseCanExecuteChanged();
}
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) => !IsExecuting && _canExecute();
public async void Execute(object parameter)
{
if (!CanExecute(parameter))
return;
IsExecuting = true;
try
{
await _execute().ConfigureAwait(true);
}
finally
{
IsExecuting = false;
}
}
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
ObservableObject is from MVVMLight framework, but you can replace this with any INotifyPropertyChanged implementation.
This can then be assigned in the code behind of your window
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
btnExe.Command = new RelayCommandAsync(ThingsToDo);
}
private async Task ThingsToDo()
{
// simulate a long running operation
await Task.Delay(TimeSpan.FromSeconds(3));
}
}
You can then define the button's look using a style in your windows's xaml file, including a trigger bound to the IsExecuting property of the command to update the image.
<Button x:Name="btnExe">
<Button.Resources>
<Image x:Key="ImgOff" Source="InterruptorOFF.png" />
<Image x:Key="ImgOn" Source="InterruptorON.png" />
<Button.Resources>
<Button.Style>
<Style BasedOn="{StaticResource {x:Type Button}}" TargetType="{x:Type Button}">
<Setter Property="Content" Value="{StaticResource ImgOff}" />
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Command.IsExecuting}" Value="True">
<Setter Property="Content" Value="{StaticResource ImgOn}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Using this construct gives the added bonus of disabling the button whilst the command is being executed (due to Command.CanExecute), preventing the user from firing off the command more than once.
Upvotes: 0
Reputation: 128147
When ThingsToDo
is a long-runnging operation, you should run it by a Task
and await
it:
await Task.Run(() => ThingsToDo());
I'd also suggest to use BitmapImage
resources instead of the more heavyweight Image
elements:
<Window.Resources>
<BitmapImage x:Key="ImON" UriSource="InterruptorON.png"/>
<BitmapImage x:Key="ImOFF" UriSource="InterruptorOFF.png"/>
</Window.Resources>
<Grid>
<Button Height="315" Width="435" Click="Button_Click">
<Image Source="{StaticResource ImOFF}" Height="315" Width="435"/>
</Button>
</Grid>
and use them in an async event handler. Also make sure the Button is disabled as long as the operation is running.
private async void Button_Click(object sender, RoutedEventArgs e)
{
var button = (Button)sender;
var image = (Image)button.Content;
var imOff = (ImageSource)FindResource("ImOFF");
if (image.Source == imOff)
{
image.Source = (ImageSource)FindResource("ImON");
button.IsEnabled = false;
await Task.Run(() => ThingsToDo());
button.IsEnabled = true;
}
else
{
image.Source = imOff;
}
}
Upvotes: 1