Splash screen in WPF MVVM application - c#

I have a MVVM WPF application from which I show a splash screen while some long task is done.
This splash screen is a typical WPF window and is very simple, it only has a label to show custom text such as "Loading..." and a spinner.
Its code-behind only has the constructor in which it is only performed the InitializeComponent and and event Window_Loaded.
From my main MVVM WPF application, when I perform a long task I instantiate this window and show the splash screen.
So now, I want to customize the text shown in the label in the splash screen. So what is the best approach?
What I have done is to pass as a parameter a string (custom message) to the constructor when I instantiate the window (Splash Screen).
Window mySplash = new SplashScreen("Loading or whatever I want");
As this splash screen is very simple, only a splash, I think it has no sense to apply MVVM here, so in the code-behind I create a private property which I set with the string passed as parameter to the constructor. Then, I bind the label in view with this private property and finally I implement INotifyPropertyChanged in the code-behind (view). There is no model, model view here.
Is that the correct way to do it? or is there any other way?
I know there is other solutions, like making public the label in the view by adding this:
x:FieldModifier="public"
and then access it once I instantiate the splash screen but I do not like this solution, I do not want to expose label outside.
ATTEMPT #1:
From my view model in main MVVM WPF application I perform below:
Window splashScreen = new SplashScreen("Loading ...");
Splash screen window:
<Window x:Class="My.Apps.WPF.SplashScreen"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<!-- Grid row and column definitions here -->
<Label Grid.Row="0" Content="{Binding Path=Message}"/>
</Grid>
</Window>
Splash screen code-behind:
public partial class SplashScreen: Window
{
public string Message
{
get
{
return (string)GetValue(MessageProperty);
}
set { SetValue(MessageProperty, value); }
}
public static readonly DependencyProperty
MessageProperty =
DependencyProperty.Register("Message",
typeof(string), typeof(System.Window.Controls.Label),
new UIPropertyMetadata("Working, wait ..."));
public SplashScreen(string message)
{
InitializeComponent();
if (!String.IsNullOrEmpty(message))
this.Message = message;
}
}
I have set a default value for Label.It will be used if it is not passed as a parameter to the constructor.
It is not working, in the xaml preview is not showing the default message in the label in the Visual Studio IDE environment. Also for some reason, when I pass as a parameter the custom message from the view model, it is not shown in the label. What am I doing wrong?

You did not set DataContext for your grid:
<Window x:Class="My.Apps.WPF.SplashScreen"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="Splash"
>
<Grid DataContext="{Binding ElementName=Splash}">
<!-- Grid row and column definitions here -->
<Label Grid.Row="0" Content="{Binding Path=Message}"/>
</Grid>
</Window>

Related

C# Snackbar as Dialog

I want to use the Snackbar from any thread. I declared my Snackbar as i should her;
https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit/wiki/Snackbar
The Wiki says that I can only access the Snackbar from a Dispatcher thread, but how do I implement this?
the user control xaml;
<materialDesign:DialogHost SnackbarMessageQueue="{Binding ElementName=MySnackbar, Path=MessageQueue}" Identifier="DialogSnackbar">
<Grid>
<!-- app content here -->
<materialDesign:Snackbar x:Name="MySnackbar" MessageQueue="{materialDesign:MessageQueue}" />
</Grid>
</materialDesign:DialogHost>
to show a dialog, but i also want to hand over a message;
implementation from https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit/wiki/Dialogs
public static async void ShowDialog()
{
var result = await DialogHost.Show("test", "DialogSnackbar", ExtendedOpenedEventHandler, ExtendedClosingEventHandler);
}
private static void ExtendedOpenedEventHandler(object sender, DialogOpenedEventArgs eventargs)
{
}
private static void ExtendedClosingEventHandler(object sender, DialogClosingEventArgs eventArgs)
{
}
the current message is displayed really weird
All seems to look right for me.
In your ShowDialog() method you only pass a string "test" as content for your DialogHost and you didn't define a DataTemplate in DialogHost.DialogContentTemplate nor a DataTemplateSelector in DialogHost.DialogContentTemplateSelector. So the default behavior of ContentControl kicks in when there is no ContentTemplate or ContentTemplateSelector defined and you don't pass the XAML elements directly as Content. This results in a TextBlock element being created for the dialog content where your string is bound to its Text property. This is exactly what your picture shows.
So to get a different result than what your picture shows you need to either pass directly your XAML elements which you want to show in your dialog (with a root container element and all buttons your dialog needs) or define a DataTemplate or DataTemplateSelector for your DialogHost in your XAML, if you want to use it in a MVVM scenario.
Look at this example from the repo if you need a hint how you can implement this.
You can use:
App.Current.Dispatcher.Invoke(()=>{
//Add the control related code stuff here
});
with the help of #Anateus i get to this solution;
in my MainWindow.xaml i declared this;
<materialDesign:DialogHost VerticalAlignment="Bottom"
HorizontalAlignment="Center"
Identifier="DialogSnackbar">
</materialDesign:DialogHost>
The DialogSnackbarView.xaml - the content of the dialog;
<UserControl ...>
<Grid>
<materialDesign:Snackbar x:Name="MySnackbar"
MessageQueue="{materialDesign:MessageQueue}" />
</Grid>
</UserControl>
to show the Dialog everywhere;
var view = new DialogSnackbarView();
view.MySnackbar.MessageQueue.Enqueue("test");
await DialogHost.Show(view, "DialogSnackbar");

Showing different controls programmatically

In my WPF app, I've to show different controls in the window for different user actions. Like, when user selects an image, I show a Image control and when the user selects a text file, I show a TextBox control. Similarly there are many controls for different user selections.
To do this I'm first doing Visibility = Visibility.Collapsed for all controls using a foreach loop, then doing Visibility = Visibility.Visible for the controls that I've to show.
Is there a more efficient way of doing this? The window flickers and is not really snappy when changing controls as there are many controls.
I always use a ContentControl for this. It produces very clean xaml code and is superfast, I've never seen any flicker even in rather loaded views. It does require any extra frameworks beyond WPF.
<ContentControl Content="{Binding Selected}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type viewModels:FirstOne}">
<!-- View code for first view goes here -->
<TextBlock>Hi</TextBlock>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:SecondOne}">
<!-- View code for second view goes here -->
<Image Source="{Binding Image}" />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
And then I have a MainViewModel handing the navigation with a property
class MainViewModel: ViewModel, INavigation
{
public ViewModel Selected
{
get { return _selected; }
private set
{
_selected = value;
RaisePropertyChanged();
}
public void Show(ViewModel viewModel) { Selected = viewModel; }
}
}
The INavigation interface is just something like so
interface INavigation { void Show(ViewModel viewModel); }
An example view model
class FirstOne: ViewModel
{
private readonly INavigation _navigation;
public FirstOne(INavigation navigation) { _navigation = navigation; }
public void ButtonClicked()
{
_navigation.Show(new SecondOne());
}
}
First, you may want to avoid doing Visibility=Visibility.Collapsed for controls that you are about to make visible. That could reduce some unnecessary screen activity.
Second, rather than having the user experience all the individual visibility changes in a serial fashion, you might want to keep your controls in a parent control that you can hide before the visibility changes take place and then unhide once the changes are complete. You could even use an opacity animation to fade it out and then back in. Of course, without seeing or understanding more about your app, I can't tell what's really appropriate.

How to manipulate a window object from another class in WPF

I'm new in WPF and C#. I know a lot of VB.NET and I'm used to the way when I call a form object like textboxes, etc. I'm calling it from another form. Now, I'm using WPF, I'm confused. Because I have a Main Window. And I want to add and item to a listbox in the Main Window from a Class. In VB.Net , its just like this.
IN FORM2
Form1.Textbox.Text = "";
Wherein I can't do it in WPF. Can someone please Help me. Thanks!
WPF windows defined in XAML have their controls publicly accessible from other classes and forms, unless you specifically mark them with the x:FieldModifier attribute as private.
Therefore, if you make an instance of your main window accessible in another class, be it a Window or anything else, you'll be able to populate controls from within this second class.
A particular scenario is when you want to update the contents of a control in your main window from a child window that you have opened on top of it. Is such a case, you may set the child window's Owner property to the current, main window, in order to access it while the child is visible. For instance, let's say you have defined these two windows:
// MainWindow
<Window x:Class="TestApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox Name="mainListBox" Height="250" HorizontalAlignment="Stretch" VerticalAlignment="Top"/>
<Button Content="Open Another Window" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="20" Click="OpenAnotherWindow_Click"/>
</Grid>
</Window>
and
// AnotherWindow
<Window x:Class="TestApplication.AnotherWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="AnotherWindow" Height="300" Width="300">
<Grid>
<Button Content="Add New Item to Main Window" HorizontalAlignment="Center" VerticalAlignment="Center" Click="AddNewItem_Click"/>
</Grid>
</Window>
each in its own XAML file.
In MainWindow's code behind, inside the button click handler, you show an instance of AnotherWindow as a dialog and set its Owner property to MainWindow's instance:
private void OpenAnotherWindow_Click(object sender, RoutedEventArgs e)
{
AnotherWindow anotherWindow = new AnotherWindow();
anotherWindow.Owner = this;
anotherWindow.ShowDialog();
}
Now, you can access the MainWindow's instance from AnotherWindow's Owner property, in order to add a new item to the ListBox control defined on it, in the button click handler in AnotherWindow's code behind:
private void AddNewItem_Click(object sender, RoutedEventArgs e)
{
MainWindow mainWindow = Owner as MainWindow;
mainWindow.mainListBox.Items.Add(new Random().Next(1000).ToString());
}
It simply adds a new random number to the ListBox, in order to show how the code accesses and modifies the control's data in MainWindow.
Pure WPF solution, but also may be easiest in your case, is using a Data Binding in WPF.
Every form's control is binded to some data on ModelView (pure MVVM approach) or to data (more or less like yuo can do it in WindowsForms). So the "only" thing you have to do is to read/write data binded to controls on UI of that form.
For example, you have TextBox on Windows and want to read a data from it.
This TextBox is binded to some string property of the class that is responsible for holding the data for the controls on that form (just an example, in real world could be 1000 other solutions, based on developer decisions). So what you need, is not to say: "window give textbox" and after read TextBox's content, but simply read binded string property.
Sure it's very simply description of a stuff. But just to give you a hint. Follow databinding link provided above to learn more about this stuff. Do not afraid of a lot of stuff there, it's after all is not a complicated idea and also pretty intuitive. To make that stuff to work in simply case you will not need to make huge efforts by me. The stuff becomes really complex when you end up into real world applications.
This will get all active windows:
foreach (Window item in Application.Current.Windows)
{
}

WPF composite Windows and ViewModels

I have a WPF Window which contains few UserControls, those controls contain another. And now, what is the most principal way how to create ViewModel for this Window and where to bind it.
I do expect that one firstly needs to create ViewModel for each of sub-controls.
There are a few ways to do this.
Inject the VM
I would recommend this method.
If your window is created in the App class like
var window = new MyWindow();
window.Show();
I would assign the VM before showing the window:
var window = new MyWindow();
window.DataContext = GetDataContextForWindow();
window.Show();
If one of your controls needs an own view model assign the VM wile creating the control instance.
DataBind
If you want to set the VM of a control you can bind the DataContext property to an VM instance provided by the surrounding VM.
<Controls:MyControl DataContext={Binding MyControlsVm} />
Code Behind
You may set the VM using the init method in code behind like
public MyWindow()
{
InitializeComponent();
DataContext = CreateViewModel;
}
You may use a trick if you don't want to create a VM for your main page:
public MyWindow()
{
InitializeComponent();
DataContext = this;
}
and just use the code behind class as VM.
I see the view as a visual representation of the ViewModel so I like WPF picking the view based on the instance of the ViewModel it wants to render.
I call this the View Locator pattern, I use this method to instantiate my view because I have found it to be very simple to implement.
It basically puts an entry in the ResourceDictionary of your app that tells WPF to use an IValueConverter to look up and instantiate the View when it comes across a ViewModel.
So a working example would be:
In your app.xaml:
<Application x:Class="MyApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml" >
<Application.Resources>
<ResourceDictionary Source="Resources.xaml"/>
</Application.Resources>
</Application>
In resources.xaml:
<DataTemplate DataType="{x:Type vm:ViewModelBase}">
<ContentControl Content="{Binding Converter={StaticResource ViewModelConverter}}"/>
</DataTemplate>
Set the DataContext of your startup Window Control e.g.
public MainWindow : Window
{
InitializeComponent();
DataContext = new MainViewModel();
}
And you're pretty much done. So if you have a MainViewModel like so:
public class MainViewModel : ViewModelBase
{
public ChildViewModel1 Child1 {get;set;}
public ChildViewModel2 Child2 {get;set;}
}
and you have a UserControl that resolves to your MainViewModel like so:
<UserControl x:Class="MainView">
<StackPanel>
<ContentPresenter Content="{Binding Child1}"/>
<ContentPresenter Content="{Binding Child2}"/>
</StackPanel>
</UserControl>
So your ViewModelConverter will return an instance of the appropriate View without any extra effort on your part.
On the child controls issue, why wouldn't one of the properties of the root view model be an instance of the child view model that you would pass onto the child control? The other option would be a converter that converts the non-view model based property into an instance of the child view model (like an adapter pattern).
You might be interested in the sample applications of the WPF Application Framework (WAF). They show how composite Views and ViewModels can be instantiated and how they interact which each other.

UserControl calling methods and using variables outside it's class

I have a UserControl that looks like this:
<UserControl x:Class="Test3.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Button Name="mybutton" Content="Button Content"/>
</Grid>
</UserControl>
And a main window that uses it like so:
<Window Name="window_main" x:Class="Test3.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Test3">
<StackPanel>
<Label Name="mylabel" Content="Old Content"/>
<local:UserControl1/>
</StackPanel>
</Window>
What I want to happen, is for mybutton's click event handler to set the content of mylabel to "New Content". However, it appears that this is impossible. Is there in fact a way to do this?
I have chosen to answer this myself since my solution ended up being a bit more complete. I don't fully understand the "right" way to do this, but this is how I did it:
Window1 window_reference = (Window1)(Window1.GetWindow((Button)sender));
After this, the children (such as other xaml controls) of the main window can be seen at compile-time.
Additionally, a more direct way of doing this is to have a public member of the UserControl like so:
namespace UIDD_Test
{
public partial class UserControl1 : UserControl
{
public Window1 window_reference;
public UserControl1()
{
InitializeComponent();
}
}
}
Then whenever appropriate, you can set that member to reference whatever window you want. In my case I have a Window1 class which is derived from Window, so I can set that member of the UserControl1 class like so:
myusercontrol.window_reference = window_main;
Where I've set up the xaml like so:
<local:UserControl1 x:Name="myusercontrol"/>
And window_main is the Name of the main window (it's a Window1 class).
There are several solutions:
The quick and dirty: on mybutton's click event handler, find the parent Window using VisualTreeHelper, then do a ((Label) window.FindName("mylabel")).Content = "New Content".
The clean WPF way: create a new class, add a property object LabelContent and a property ICommand ChangeContentCommand, that will change LabelContent on execution. Set this class as the DataContext of the window, bind the Content of mylabel to LabelContent and the Command property of mybutton to ChangeContentCommand (the user control will inherit the data context).
The simplest way to do what you describe is to take advantage of event routing and just add a handler in the Window XAML:
<StackPanel ButtonBase.Click="Button_Click">
<Label Name="mylabel" Content="Old Content"/>
<local:UserControl1/>
</StackPanel>
and the handler method:
private void Button_Click(object sender, RoutedEventArgs e)
{
mylabel.Content = "New Content";
}
I suspect you probably have some more complications to this in your real application so you may need to do more to verify that the click is coming from the correct button by checking some properties on it.

Categories