Open maiorfi opened 3 years ago
if your initialization is async , you can set it in loadedCommand
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Loaded">
<core:InvokeCommandAction Command="{x:Bind ViewModel.LoadCommand}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
private ICommand _loadCommand;
public ICommand LoadCommand
{
get
{
if (_loadCommand == null)
{
_loadCommand = new RelayCommand(() =>
{
});
}
return _loadCommand;
}
}
@Sergio0694 @jamesmcroft this is something I see come up a lot in terms of folks needing to call some async method to load data into their UI when it's starting up. We should provide docs around this and have an example in our sample app which shows the best-practice to handle this scenario.
I generally do this with a XAML behavior wired up to the Loaded
event, and an AsyncRelayCommand
.
That's pretty handy as it also makes it easy to display eg. a loading bar while the viewmodel is initializing, as the async command has the IsRunning
property with notification support. We should definitely call this out in the docs, sure! 😄
Great, thanks.
reopening so an link to the docs that show this can be added here.
For sake of completeness (since it isn't so easy to find anything one needs in a single place):
(After having added Microsoft.Xaml.Behaviors.Wpf NuGet package)
View:
<UserControl x:Class="SampleShellApp.SecondSampleUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:Behaviors="http://schemas.microsoft.com/xaml/behaviors"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Behaviors:Interaction.Triggers>
<Behaviors:EventTrigger EventName="Loaded">
<Behaviors:InvokeCommandAction Command="{Binding LoadCommand}"/>
</Behaviors:EventTrigger>
</Behaviors:Interaction.Triggers>
<!-- UserControl content here -->
</UserControl>
Viewmodel:
public class MyViewModel : ObservableRecipient
{
public MyViewModel ()
{
LoadCommand = new AsyncRelayCommand(loadCommand);
}
public IAsyncRelayCommand LoadCommand { get; }
async Task loadCommand()
{
Trace.TraceInformation("Starting loadCommand()...");
// Simulate long operation...
await Task.Delay(TimeSpan.FromSeconds(3));
// Example of GUI update...
MySampleBoundProperty="default-property-value";
Trace.TraceInformation("...loadCommand() done.");
}
}
@maiorfi thank you so much! Code clear and straight to the point. :)
Please, this should be added to the docs somewere. I'm still learning about xaml, wpf and mvvm and I spent almost an hour thinking about an elegant solution to this exact problem.
@Sergio0694 provided pattern look good to you? Any feedback?
Happy for anyone to submit a PR to move this along in the meantime too, we can always do feedback/tweeks in a PR too.
This code introduces a new problem:
Each time the user activates the UserControl
, the Loaded
event of the UserControl
is executed repeatedly.
To solve this problem a new Initialized
property can be added:
public class MyViewModel : ObservableRecipient
{
protected bool Initialized { get; set; }
public MyViewModel ()
{
LoadCommand = new AsyncRelayCommand(loadCommand);
}
public IAsyncRelayCommand LoadCommand { get; }
async Task loadCommand()
{
if(Initialized) return;
Trace.TraceInformation("Starting loadCommand()...");
// Simulate long operation...
await Task.Delay(TimeSpan.FromSeconds(3));
// Example of GUI update...
MySampleBoundProperty="default-property-value";
Trace.TraceInformation("...loadCommand() done.");
Initialized = true;
}
}
This code introduces a new problem: Each time the user activates the
UserControl
, theLoaded
event of theUserControl
is executed repeatedly.
Can you define what you mean by "the user activates the UserControl
"?
Also, from your example, you probably want the setter of the Initialized
property to be private.
Can you define what you mean by "the user activates the
UserControl
"?
There is a TabControl
control in the Window
, and the TabControl
control has two TabItem
controls. One UserControl
is placed in each TabItem
control.
The Loaded
event of the each UserControl
will be triggered when the each TabItem
is activated.
MainWindow.xaml
file:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
Title="MainWindow" Width="800" Height="450">
<TabControl>
<TabItem Header="Tab 1">
<local:UserControl1 />
</TabItem>
<TabItem Header="Tab 2">
<local:UserControl2 />
</TabItem>
</TabControl>
</Window>
UserControl1.xaml
file:
<UserControl x:Class="WpfApp1.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Loaded="UserControl1_OnLoaded">
<Grid>
<TextBlock Text="Content 1" />
</Grid>
</UserControl>
UserControl1.cs
file:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
private void UserControl1_OnLoaded(object sender, RoutedEventArgs e)
{
Trace.WriteLine("UserControl1_OnLoaded"); // This message will be printed if the `TabItem` is activated.
}
}
Also, from your example, you probably want the setter of the
Initialized
property to be private.
The Initialized
event is not a good choice. ViewModel
s are injected via ViewModelLocator
generally. The ViewModel
has not been injected yet when the Initialized
event is triggered. The view cannot be accessed by the Initialized
event.
Hi.
What is the right way to initialize viewmodels (created via IoC)?
I am wondering what kind of pattern should I follow both for sync and async initialization.
Thanks.