How to Unit Test the WPF View Files like Sample.xaml and sample.xaml.cs files in mvvm pattern
In WPF you don't need to unit test the xaml. Xaml are just UI that are bound to the view model. What you really need to test is the view model after all, the state of the application should be in the view model not in the xaml.
Let's say for example you want to test if a button is visible after a certain logic. What you need to do is to create a property in the viewmodel (i.e. IsButtonShown) which is a boolean and bind it to the visibility of the button that has a boolean to visibility converter.
<Button Content="Click Me!" Visibility="{Binding IsButtonShown, Converter={StaticResource booleanToVisibility}}" />
Through binding you can now see the state of every property of the UI from the viewmodel so that you don't need to create a unit test for the xaml.
Now in your unit test, you can do something like:
[TestMethod]
public void TestMethod1()
{
viewModel.InsideThisMethodWeSetIsButtonShownToFalse();
Assert.IsTrue(!viewModel.IsButtonShown);
}
Related
Many questions have been asked about using a XAML element from the View in the ViewModel. But what about defining a XAML element in the ViewModel and using that in the XAML markup of the View? So that the ViewModel knows about the element from the get-go(and owns the element).
I was trying to do this thinking it would be more MVVM-compliant--the ViewModel doesn't have to know about the entire View in order to use this component. But I recognize it's still not really in the spirit of MVVM to define a XAML element in the ViewModel. But at this point, I just want to know if this technique is possible.
I know I can define a property in the ViewModel, and then bind that to a property of a XAML element defined in the View. E.g. defining a string in the ViewModel and binding that to the Text property of a TextBlock XAML element. But is it possible to simply define the XAML element itself in the ViewModel and use that in the XAML markup of the View?
Example code. I'm using C++/WinRT with WinUI 3:
MainWindowViewModel.idl
namespace MyApp
{
runtimeclass FooViewModel
{
MainWindowViewModel();
Int32 IntProperty; // I can bind this to a XAML element's property
Microsoft.UI.Xaml.Controls.Frame Frame; // Can I use this in the View?
}
}
MainWindow.idl (View)
import "ViewModel/MainWindowViewModel.idl";
namespace MyApp
{
runtimeclass MainWindow : Micorosoft.UI.Xaml.Window
{
MainWindow();
MainWindowViewModel MainWindowViewModel{ get; };
}
}
MainWindow.xaml (View's XAML)
<Window
x:Class="MyApp.MainWindow"
//...
>
<StackPanel>
// I can bind to a property of MainWindowViewModel as such:
<Slider Minimum="0" Maximum="{x:Bind MainWindowViewModel.IntProperty}" />
// Is there a way to use the Frame defined in MainWindowViewModel here, or can you only create a new Frame instance?
<Frame/>
</StackPanel
</Window>
Is it possible to simply define the XAML element itself in the
ViewModel and use that in the XAML markup of the View?
Yes, It will be work.
I tested it in WinUI3 C# and the code works fine.
When I try to use the method to inflate my XAML:
MenuBarItem item = new().LoadFromXaml("<MenuBarItem Text=\"Session\"><MenuFlyoutItem Text=\"New\"/><MenuFlyoutItem Text=\"Save\"/><MenuFlyoutItem Text=\"Load\"/></MenuBarItem>");
the MenuBarItem is created and Text properly assigned but all the MenuFlyoutItems are ommited and not added to the menu.
After reading Load XAML at runtime documentation and particularly the "The LoadFromXaml method can be used to inflate any XAML" and the examples given, I assumed that I can throw any valid XAML into it - from a single button, to a DataTemplate of a ListView, a MenuBarItem for a menu, to a whole ContentPage and it should work. But it's not working in this case - I get Microsoft.Maui.Controls.Xaml.XamlParseException and System.Reflection.TargetInvocationException.
Is this behavior a bug or is documentation missing some details about loading XAMLs?
When I enclose the MenuBarItem in a ContentPage's MenuBarItems like this:
new ContentPage().LoadFromXaml("<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<ContentPage\r\n\txmlns=\"http://schemas.microsoft.com/dotnet/2021/maui\"\r\n\txmlns:x=\"http://schemas.microsoft.com/winfx/2009/xaml\"\r\n\tx:Class=\"LoadRuntimeXAML.CatalogItemsPage\"\r\n\tTitle=\"Catalog Items\">\r\n\t<ContentPage.MenuBarItems>\r\n\t\t<MenuBarItem Text=\"Session\">\r\n\t\t\t<MenuFlyoutItem\r\n\t\t\t\tText=\"New\"/>\r\n\t\t\t<MenuFlyoutItem\r\n\t\t\t\tText=\"Save\"/>\r\n\t\t\t<MenuFlyoutItem\r\n\t\t\t\tText=\"Load\"/>\r\n\t\t</MenuBarItem>\r\n\t</ContentPage.MenuBarItems>\r\n</ContentPage>");
it inflates without error and then when I assign elements from the inflated ContentPage to the MainPage's MenuBarItems they display well. But this is an ugly workaround because I don't need a whole ContentPage, just the MenuBarItem.
Your XAML is not complete, thus cannot be parsed.
What the ContentPage has, that your XAML lacks, is the various xmlns lines, that specify the XML elements used in the XAML.
I have not tested, but try replacing <MenuBarItem with
<MenuBarItem\r\n\txmlns=\"http://schemas.microsoft.com/dotnet/2021/maui\"\r\n\txmlns:x=\"http://schemas.microsoft.com/winfx/2009/xaml\"\r\n
Adapt as needed. Any whitespace can be used anywhere \r\n is shown.
If it doesn't work, also prefix with:
<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n
But I believe that is optional.
As an aside, anything that can be done in XAML, can instead be done in C#. C# markup.
C#, being a complete computational language, can often create dynamic UI more easily than XAML, if you are building a UI that depends on different conditions.
A convenient approach in C#, is to define "helper" methods, that take whatever parameters you want, and creates a specific element. That you add to a given parent element, either via C# markup, or methods of a parent layout class.
Its easy to write helper methods that call other helper methods, to build up a whole layout to your specs, controlled at each step by the parameters that matter to you.
At the top level, you might end up with code like this:
// use custom helper methods and methods of "Grid" class.
Grid grid = MyCreateGrid();
grid.Children.Add(MyCreateRowLabel(text), 1, 0);
grid.Children.Add(
// OR use C# markup
new StackLayout
{
Children =
{
new Label().Text("Code:"),
...
}
},
1, 1
);
...
From the official document, it's only using LoadFromXaml for single view or a complete contentPage. I also tried LoadFromXaml for <MenuBarItem Text=\"Session\"><MenuFlyoutItem Text=\"New\"/><MenuFlyoutItem Text=\"Save\"/><MenuFlyoutItem Text=\"Load\"/></MenuBarItem>, and just you said that:
the MenuBarItem is created and Text properly assigned but all the MenuFlyoutItems are ommited and not added to the menu.
But you can achieve it by doing this:
MainPage.xaml:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiApp_loadXaml.MainPage"
x:Name="contentPage">
<Button Text="click" Clicked="Button_Clicked" HeightRequest="50"/>
</ContentPage>
MainPage.xaml.cs:
private void Button_Clicked(object sender, EventArgs e)
{
var xaml = "<MenuBarItem Text=\"Session\"></MenuBarItem>";
var xaml1 = "<MenuFlyoutItem Text=\"New\"/>";
var xaml2 = "<MenuFlyoutItem Text=\"Save\"/>";
var xaml3 = "<MenuFlyoutItem Text=\"Load\"/>";
MenuFlyoutItem menuFlyoutItem_1 = new MenuFlyoutItem().LoadFromXaml(xaml1);
MenuFlyoutItem menuFlyoutItem_2 = new MenuFlyoutItem().LoadFromXaml(xaml2);
MenuFlyoutItem menuFlyoutItem_3 = new MenuFlyoutItem().LoadFromXaml(xaml3);
MenuBarItem item = new MenuBarItem();
item.LoadFromXaml(xaml);
item.Add(menuFlyoutItem_1);
item.Add(menuFlyoutItem_2);
item.Add(menuFlyoutItem_3);
contentPage.MenuBarItems.Add(item);
}
It works well.
I have a UWP application based on the Template10 hamburger template. I added the following modal dialog.
<Controls:ModalDialog x:Name="LoginModal"
CanBackButtonDismiss="False"
DisableBackButtonWhenModal="True">
<Controls:ModalDialog.ModalContent>
<views:LoginView x:Name="loginPart"
HorizontalAlignment="Center"
VerticalAlignment="Center"
HideRequested="LoginHide"
LoggedIn="LoginLoggedIn" />
</Controls:ModalDialog.ModalContent>
</Controls:ModalDialog>
My login view is declared like this
[Export]
public sealed partial class LoginView : Page, IView
{
....
}
For some reason the xaml extract above gives me a design time error saying "Cannot create an abstract class." The project builds and runs ok but the xaml designer won't work.
Anyone know what I'm doing wrong here?
LoginView would need to be a UserControl not a Page. The other properties would be added to that codebehind of that usercontrol to allow for interaction with the ModalDialog
From the Samples folder...
https://github.com/Windows-XAML/Template10/blob/master/Samples/Search/Controls/LoginPart.xaml.cs
if this doesn't work then I would suspect the MEF [Export]
In my case in need change this
<DrawinSurfaceBackgroundGridgx:Name="DrawingSurfaceBackground"Loaded="DrawingSurfaceBackground_Loaded">
</DrawingSurfaceBackgroundGrid>
to this
<Grid>
<phone:WebBrowser Name="MiniBrowser" Visibility="Collapsed"/>
<!--LayoutRoot is the root grid where all page content is placed-->
<DrawingSurfaceBackgroundGridx:Name="DrawingSurfaceBackground"Loaded=
"DrawingSurfaceBackground_Loaded">
</DrawingSurfaceBackgroundGrid>
</Grid>
When unity3d build default project there is a default MainPage. I need too add a webbrowser component in this mainpage from my unity3d plugin. And then i need to call browser navigate and subscribe to some browser events like loadCompleted Is it possible? Plese give me an example
You can get add/remove items programatically. If you can access the MainPage class you can get to it's content, which is the grid, and to its children which is an UIElementCollection which implements IList.
IList has an insert method which allows you to insert an element at a specified index, in your case 0, here a full example:
MainPage mainPage = new MainPage();
var miniBrowser = new WebBrowser
{
Visibility = Visibility.Collapsed;
};
mainPage.Content.Children.Insert(0, miniBrowser);
I didn't add a name to the WebBrowser class since because it is being added programatically it really makes no difference (the Name property is used at design time (xaml is parsed at design time and at runtime[in the InitializeComponent method]) to generate a property in a partial class (MainPage.i.g.cs/MainPage.g.cs)).
The way you get hold of MainPage is usually from Application.Current.Content (Application.Current will contain a Frame whose Content is MainPage).
I'm developing a WP7 application, and I'm generating a listbox with a few items. I was wondering if there is a way to preview how the layout would look. So far, since the elements don't exist, I can't "preview" them.
Is there some way to feed some dummy data or other methods that would help in previewing xaml layouts ?
First - it helps if you use MVVM, or at least ItemsSource binding + ItemTemplate to display your items. Once you are there - Expression Blend has some great tools for sample data.
You go to Data tab, click Create Sample Data/New Sample Data. It will create a sample data as XAML and bind your page to it like that:
d:DataContext="{d:DesignData SampleData/MainViewModelSampleData.xaml}"
Then you can add new properties, model collections with different data types and it will automatically generate some data you can use in your XAML.
You should provide a designer data.
There are several ways to do it.
One of the simplest, is to provide a DataContext in your XAML declaration for designer to use when rendering your page display.
In Xaml page declaration:
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
d:DataContext="{d:DesignInstance local:DesignerSampleData, IsDesignTimeCreatable=True}"
The sample data class should have the data that your visual elements bind to:
public class DesignerSampleData: INotifyPropertyChanged
{
public DesignerSampleData()
{
_sampleData = "My test string that will display in VS designer for preview";
}
private String _sampleData;
public String SampleData
{
get { return _sampleData; }
set
{
if (value != _sampleData)
{
_sampleData = value;
NotifyPropertyChanged("SampleData");
}
}
}
In xaml bind to SampleData:
<TextBlock Text="{Binding SampleData}" />