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

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");

Related

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

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.

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.

Order of element gets changed when it's focused in Grid layout

I have placed some elements in a grid without any Row & Column definitions.
<Grid>
<Button x:Name="button" Text="{Binding ButtonText}" CornerRadius="5" />
<Image HorizontalOptions="Start" VerticalOptions="Center" Source="{Binding IconImageSource}" InputTransparent="True"/>
</Grid>
Here, the image is the top element. When the button got tapped, the image goes to back. Why is this happening and is there any way to stop it?
This reason is from Fast Renderers .
Fast renderers are available for the following controls in Xamarin.Forms on Android:
Button
Image
Label
Frame
Functionally, these fast renderers are no different to the legacy renderers. From Xamarin.Forms 4.0 onwards, all applications targeting FormsAppCompatActivity will use these fast renderers by default.
Fast renderers can be overridden with the following approaches:
Enabling the legacy renderers by adding the following line of code to your MainActivity class before calling Forms.Init:
Forms.SetFlags("UseLegacyRenderers");
Using custom renderers that target the legacy renderers. Any existing custom renderers will continue to function with the legacy renderers.
Specifying a different View.Visual, such as Material, that uses different renderers. For more information about Material Visual, see Xamarin.Forms Material Visual.
So , here Solution is adding the following to your MainActivity.OnCreate before the global::Xamarin.Forms.Forms.Init(this, savedInstanceState) call:
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
global::Xamarin.Forms.Forms.SetFlags("UseLegacyRenderers"); //add code here
global::Xamarin.Forms.Forms.Init (this, bundle);
LoadApplication (new App ()); // method is new in 1.3
}
The effect :

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.

CompositeCollection containing an ICollectionView

I'm trying to implement a tab control, where each item comes from an ICollectionView of my viewmodel. Each tab page, for the items from the ICollectionView will be the same. However, I would like there to be an extra tab page for configuration options.
So an example tab header 'screenshot' might be:
tabA | tabB | tabC | config
on another instance, it could be
tabA | config
or
config
I can define the header for each item using ItemTemplateSelectors, and the content using the ContentTemplateSelectors. So that bit should be okay.
I'm having trouble with adding the config page item since I do not know where to add it. I thought I could set the tab's ItemsSource to be a CompositeCollection, where the final item is the config page object. I have failed to achieve this.
In the following example, I can view the tab headers being populated correctly according to the designer sample data which I have set up - I have not yet added the config page.
<controls:MetroTabControl ItemsSource="{Binding View}">
<controls:MetroTabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value.siteDisplayName}" />
</DataTemplate>
</controls:MetroTabControl.ItemTemplate>
<controls:MetroTabControl.ContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value.siteComment}"/>
</DataTemplate>
</controls:MetroTabControl.ContentTemplate>
</controls:MetroTabControl>
As you see, I have set the ItemsSource to be {Binding View}. This "View" comes from my ViewModel and is an ICollectionView.
Ideally i'd be able to do some magic like:
<controls:MetroTabControl>
<controls:MetroTabControl.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding View}"/>
<SomeConfigPageObject/>
</CompositeCollection>
</controls:MetroTabControl.ItemsSource>
...snip...
</controls:MetroTabControl>
But the problem is that when I do the above, the designer preview of the control acts as if there are no items in the ItemsSource.
For reference, each item in the {Binding View} is a object which contains a Value property, the value property containing an object that contains, in this example, a siteDisplayName and siteComment.
For reference, the DataContext for the tab is defined the dockpanel that contains it, as follows.
<DockPanel DataContext="{Binding Source={StaticResource Configurator}}"
d:DataContext="{d:DesignInstance cfuid:ConfigSiteVMSampleData, IsDesignTimeCreatable=true}"
LastChildFill="True">
For reference, the Configurator is my viewmodel and is instantiated in the xaml as:
<UserControl.Resources>
<ResourceDictionary>
...snip...
<cfvmc:ConfigSiteVM x:Key="Configurator" />
...snip...
So, the actual question would be:
How do I add my "config page" at the end of the tab control? Preferably via using the above-hoped method of adding an extra config-page object on the CompositeCollection; however if this is not possible [1] i'm open for suggestions.
[1] I think it doesn't work because the {Binding View} is an ICollectionView and the CompositeCollection requires a "collection" and doesn't accept a "view"
Thank you.
Peter.
I decided to do it through code behind. This means that I do lose my ability to use the design-time data to preview my UI; but it works at run time.
So, in the xaml I have.
<controls:MetroTabControl Grid.Column="0" Grid.ColumnSpan="2"
Grid.Row="0" Grid.RowSpan="2"
ItemsSource="{Binding ElementName=ucMe, Path=TabSitesCollection}">
Where ucMe is the UserControl and TabSitesCollection is a
protected CollectionViewSource m_TabSitesCollectionViewSource;
protected CompositeCollection m_TabSitesComposites;
public ICollectionView TabSitesCollection
{
get { return m_TabSitesCollectionViewSource.View; }
}
That gets initialised in the constructor as follows
public ConfigSiteView()
{
m_TabSitesComposites = new CompositeCollection();
m_TabSitesCollectionViewSource = new CollectionViewSource();
m_TabSitesCollectionViewSource.Source = m_TabSitesComposites;
InitializeComponent();
}
Then, on the Loaded event I can do
m_TabSitesComposites.Add(new CollectionContainer() { Collection = GetModel.View });
m_TabSitesComposites.Add(new TabItem() { Header = "hi" });
m_TabSitesComposites.Add(new TabItem() { Header = "ho" });
This results in almost my desired UI
I now simply need to spiff up my settings tab item and i'm done.
For reference, the xaml designer does not have any preview data - Unless I change the xaml so that the preview loads up (which then breaks the actual execution)
It would have been nice to have it both work while running, and on preview, but I haven't figured out how to do that, and it's not a current priority.