Can I define a XAML element in the ViewModel and use it directly in the View using C++/WinRT and WinUI 3? - xaml

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.

Related

LoadFromXaml parse exception inflating MenuBarItem

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.

Inheriting descendant of WinUI-UserControl does not inherit all styles

When inheriting a TextBox, not all styles are respected by the child (see image in appendix).
<TextBox
x:Class="AdvoTools.PerfectTimeNative.UI.Controls.TextInput"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"/>
using Microsoft.UI.Xaml.Controls;
namespace AdvoTools.PerfectTimeNative.UI.Controls
{
public sealed partial class TextInput : TextBox
{
public TextInput() => InitializeComponent();
}
}
I tried different approaches:
Inheriting as above results in certain styles missing;
Copying the hardcoded TextBox styles from generic.xaml and setting their target to the inherited control results into the same effect;
using templated controls as mentioned here does not seem to work either. When I create it through the templated control preset for UWP and change the namespaces from Windows to Microsoft, the control is empty (the generated generic.xaml does not appear to be respected at all. Deleting it altogether makes no difference);
Defining a style with the BasedOn property has the same reuslt as the first approach.
Note:
The blue bottom border thickness does not increase when the TextBox is focused (not visible here);
the corder radius is 0.
This should be a no-brainer but I am really struggling at the moment.
Create a standalone custom class:
public class MyCustomTextBox : TextBox
{
public MyCustomTextBox()
{
DefaultStyleKey = typeof(MyCustomTextBox);
}
}
And add the following default template for it to themes/generic.xaml:
<Style TargetType="local:MyCustomTextBox" BasedOn="{StaticResource DefaultTextBoxStyle}" />
Then it should look like a default TextBox.

Xamarin.Forms loading Xaml with LoadFromXaml is not creating contained objects for a StackLayout

I'm trying to add to an existing xamarin.forms page, chunks of xaml that will be generated dynamically.
I'm starting my research using the following example from the official docs.
Everything works fine there, but when I try to change the string with the button def for a string with the stacklayout with the button inside, only the stacklayout is inflated, with no children at all.
That's weird, because you can inflate a full page in the example, but it looks like I'm missing something here.
Any advice about how to use LoadFromXaml for partial composite objects?
// MainPage.xaml.cs
void OnLoadButtonClicked(object sender, EventArgs e)
{
string navigationButtonXAML = "<StackLayout><Button Text=\"Navigate\" /></StackLayout>";
var sl = new StackLayout().LoadFromXaml(navigationButtonXAML);
_stackLayout.Children.Add(sl);
}
From the official doc, it's only using LoadFromXaml for single view or a complete contentPage, I also tried LoadFromXaml for layout, and it's loading the layout without its children. For loading layout with children, I'm try debugging with source code, will update later, and as a workaround, you can use layout with contentview like this:
string navigationStackLayoutXAML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><ContentView xmlns=\"http://xamarin.com/schemas/2014/forms\" xmlns:x=\"http://schemas.microsoft.com/winfx/2009/xaml\" xmlns:d=\"http://xamarin.com/schemas/2014/forms/design\" xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" mc:Ignorable=\"d\" x:Class=\"LoadRuntimeXAML.CustomerViewDemo\"> <ContentView.Content> <StackLayout > <Label Text=\"Hello Xamarin.Forms!\" /> <Button Text=\"SECONDB\"/> </StackLayout> </ContentView.Content> </ContentView>";
ContentView contentView = new ContentView().LoadFromXaml(navigationStackLayoutXAML);
_stackLayout.Children.Add(contentView);
Although it's xaml is a little bit more complex, but it can use predefined attributes just as in .xaml, also, elements inside the contentView will be accessible through:
Button secondB = contentView.FindByName<Button>("secondB");

What element to use if I only need an placement target element

I want to show a flyout at a specific place. I want to specify a placement target element in XAML, but I want to make sure I am using the "lightest" element possible, given that I don't want that element to ever be visible or interacted with.
Is there a "recommended" or "correct" element to use? If not, what would be the "lightest" element to use? Or am I overthinking this and should just use a button?
I want to show a flyout at a specific place.
The place of FrameworkElements are based on the panel that you are using to hold them. If you want to show your flyout based on a FrameworkElement being placed in a specific place, you can use Canvas to position your FrameworkElement.
I want to specify a placement target element in XAML, but I want to make sure I am using the "lightest" element possible.
Flyout.ShowAt takes FrameworkElement as it's placement target. So I think the "lightest" element would be an empty custom FrameworkElement like below:
public class MyElement:FrameworkElement
{
}
And you can put it into XAML and use Canvas to position it:
<Page
x:Class="PopupSample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:PopupSample"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Canvas>
<Button Name="btnClick" Canvas.Left="50" Canvas.Top="500" Click="btnClick_Click">Click Me</Button>
<local:MyElement x:Name="myEle" Canvas.Left="100" Canvas.Top="100"></local:MyElement>
</Canvas>
Code-Behind:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
private void btnClick_Click(object sender, RoutedEventArgs e)
{
Flyout flyout = new Flyout();
TextBlock tbContent = new TextBlock
{
Text= "this is a flyout content"
};
flyout.Content = tbContent;
flyout.ShowAt(myEle);
}
}
Grid is a pretty light element, it's just a simple Panel-derived class without any child elements. Button is a Control, meaning it has a template which will create many child elements that make up its visual appearance.
Are you saying you want to use a dummy element just for the purpose of specifying the position of the flyout which you will show programmatically? If you want to avoid the element altogether, then maybe a Popup would be a better choice.

is it possible to edit MainPage.xaml from C# code?

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).