LoadFromXaml parse exception inflating MenuBarItem - xaml

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.

Related

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

How to implement UWP the right way

I run often into many problems which leads to refactoring my code...
That is why I want to ask for some recommendations.
The problems I'm running into are:
1) Providing data to XAML
Providing simple data to control value instead of using a value converter. For instance I have a color string like "#FF234243" which is stored in a class. The value for the string is provided by a web application so I can only specify it at runtime.
2) UI for every resolution
In the beginnings of my learning I got told that you can create a UI for every possible resolution, which is stupid.
So I've written a ValueConverter which I bind on an element and as ConverterParameter I give a value like '300' which gets calculated for every possible resolution... But this leads to code like this...
<TextBlock
Height={Binding Converter={StaticResource SizeValue}, ConverterParameter='300'}
/>
3) DependencyProperties vs. NotifyProperties(Properties which implement INotifyPropertyChanged) vs. Properties
I have written a control which takes a list of value and converts them into Buttons which are clickable in the UI. So I did it like this I created a variable which I set as DataContext for this specific Control and validate my data with DataContextChanged but my coworker mentioned that for this reason DependencyProperties where introduced. So I created a DependecyProperty which takes the list of items BUT when the property gets a value I have to render the buttons... So I would have to do something like
public List<string> Buttons
{
get { return (List<string>)GetValue(ButtonsProperty); }
set
{
SetValue(ButtonsProperty, value);
RenderButtons();
}
}
// Using a DependencyProperty as the backing store for Buttons. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ButtonsProperty =
DependencyProperty.Register("Buttons", typeof(List<string>), typeof(MainPage), new PropertyMetadata(""));
private void RenderButtons()
{
ButtonBar.Children.Clear();
ButtonBar.ColumnDefinitions.Clear();
if(Buttons != null)
{
int added = 0;
foreach (var item in Buttons)
{
var cd = new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) };
var btn = new Button() { Content = item };
ButtonBar.ColumnDefinitions.Add(cd);
ButtonBar.Children.Add(btn);
Grid.SetColumn(btn, added);
}
}
}
And have to use it like this:
<Controls:MyControl
x:Name="ButtonBar" Button="{Binding MyButtons}">
</Controls:MyControl>
Since these are a lot of topics I could seperate those but I think that this is a pretty common topic for beginners and I have not found a got explanation or anything else
1. Providing data to XAML
There are two options: prepare data in the ViewModel or to use converter.
To my mind using converter is better since you can have crossplatform viewModel with color like you mentioned in your example and converter will create platform dependent color. We had similar problem with image. On android it should be converted to Bitmap class, while on UWP it's converted to BitmapImage class. In the viewModel we have byte[].
2. UI for every resolution
You don't need to use converter, since Height is specified in effective pixels which will suit all the required resolutions automatically for you. More info can be found at the following link:
https://learn.microsoft.com/en-us/windows/uwp/design/layout/layouts-with-xaml
There are two options how to deal with textblock sizes:
a) Use predefined textblock styles and don't invent the wheel (which is the recommended option):
https://learn.microsoft.com/en-us/windows/uwp/design/style/typography#type-ramp
Or
b) Specify font size in pixels. They are not pixels, but effective pixels. They will be automatically scaled on different devices:
https://learn.microsoft.com/en-us/windows/uwp/design/style/typography#size-and-scaling
Furthermore, use adaptive layout to have different Layout for different screen sizes.
3) DependencyProperties vs. NotifyProperties(Properties which implement INotifyPropertyChanged) vs. Properties
As per your code you can try to use ListView or ItemsControl and define custom item template.
DependencyProperties are created in DependencyObject and are accessible in xaml. All controls are inherited from DependencyObjects. Usually you create them when you want to set them in xaml. They are not stored directly in the objects, but in the global dictionary and resolved at runtime.
DependencyProperties were created long time ago and you can find lots of links which explain them in details:
http://www.wpftutorial.net/dependencyproperties.html
https://techpunch.wordpress.com/2008/09/25/wpf-wf-what-is-a-dependency-property/
When should I use dependency properties in WPF?
What is a dependency property? What is its use?
What is a dependency property?
INotifyPropertyChanged INPC are the central part of MVVM. You bind your view to viewModel which implements INPC and when you change value of the property control is notified and rereads the new value.
Download the following video in high resolution which explains MVVM in details (by Laurent Bugnion):
https://channel9.msdn.com/Events/MIX/MIX11/OPN03
MVVM: Tutorial from start to finish?
Normal properties are used in model classes or when there is no need to notify UI regarding changes.

InputScope dependency property, XAML designer problems + errors

I have a control that consists of a button and a textbox.
I wanted to set the input scope of the textbox, so I introduced a new dependency property:
public InputScope InputScope
{
get { return (InputScope)GetValue(InputScopeProperty); }
set { SetValue(InputScopeProperty, value); } // Notify prop change
}
public static readonly DependencyProperty InputScopeProperty =
DependencyProperty.Register(nameof(InputScope), typeof(InputScope), typeof(SearchControl), new PropertyMetadata(DependencyProperty.UnsetValue));
In XAML:
<controls:SearchControl InputScope="Number" /> <!-- etc... -->
(Obviously assigning it to the InputScope property of the textbox in the style of this custom control.)
My problem: While this works, the numeric keyboard gets shown when focused, but I have blue underline in the XAML, and also an error message: The TypeConverter for "InputScope" does not support converting from a string.
Is there a way to fix it without a dirty hack?
Is there a way to fix it without a dirty hack?
You could implement a type converter. Please refer to Tim Heuer's blog post for more information and an example:
Implementing a type converter in UWP XAML: http://timheuer.com/blog/archive/2017/02/15/implement-type-converter-uwp-winrt-windows-10-xaml.aspx
You may also want to read this:
WinRT Replacement for System.ComponentModel.TypeConverter

Binding a ScrollViewer from ViewModel to View

I Build a scrollViewer and its elements in my ViewModel, and it's built into a property FrameworkElement PageElement I rebuild the pageElement every time some event happens, I want to bind the PageElement to a real scrollViewer in the View so that whenever I change pageElement, it draws itself in it's view.
Let me give you a little armchair advice. I don't know the details of your project but the details in your question make me draw a few conclusions.
First, to have your view model create UI elements is not wrong. But it is really unusual. It sounds like you might be missing the concept of data template or data template selector.
Using a data template allows you to have a rich presentation of data that is built as the individual record is generated and rendered in a repeater or in a single content control.
Using a data template selector allows you to have various different presentations of data that using code-behind logic will switch between based on data or other criteria.
Ref on templates: http://blog.jerrynixon.com/2012/08/windows-8-beauty-tip-using.html
Second, to have your UI be re-generated as the result of an event being raised sounds like a short path to performance problems.
Every time you manually create elements and add them to the visual tree, you put your app at risk of binding lag while the layout is re-rendered. Run your app on an ARM and I bet you may already see it. Then again, a simplistic UI may not suffer from this general rule of thumb.
Because I do not know the event, I cannot presume it is frequently occurring. However, if it is frequently occurring, then even a simplistic UI will suffer from this.
Now to answer your question
Sherif, there is no write-enabled property on a scrollviewer that will set the horizontal or vertical offset. The only way to set the offset of a scrollviewer is to call changeview().
var s = new ScrollViewer();
s.ChangeView(0, 100, 0);
You cannot bind to a method, so binding to something like this is a non-starter without some code-behind to read the desired offset and calling the method directly.
Something like this:
public sealed partial class MainPage : Page
{
MyViewModel _Vm = new MyViewModel();
ScrollViewer _S = new ScrollViewer();
public MainPage()
{
this.InitializeComponent();
this._Vm.PropertyChanged += (s, e) =>
{
if (e.PropertyName.Equals("Offset"))
_S.ChangeView(0, _Vm.Offset, 0);
};
}
}
public class MyViewModel : INotifyPropertyChanged
{
private int _Offset;
public int Offset
{
get { return _Offset; }
set
{
_Offset = value;
PropertyChanged(this, new PropertyChangedEventArgs("Offset"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
But let me caution you. The offset will need to be based on something. And those variables may change based on the window size, the font size, scaling from transforms, and lots of other factors. The code above will work most of the time, but it will possible fail frequently on other devices.
So, what to do? My recommendation is that you code this in your code-behind, monitoring for whatever scenario you feel would require a scroll, and simply programmatically scroll it from bode-behind. Beware, though, programmatically scrolling a scrollviewer could make your UI confusing to the user.
You know your app. You will have to choose.
Best of luck!

Preview xaml layout with specific sample data

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}" />