Setting Window.Content to ViewModel - simple data template not working - mvvm

Trying to get my head around MVVM, and getting a simple window to render its viewmodel as a view via data templating.
in App.xaml:
<Application.Resources>
<DataTemplate DataType="{x:Type vm:TestViewModel}">
<vw:TestView />
</DataTemplate>
</Application.Resources>
View definition:
<UserControl x:Class="MyNamespace.TestView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock>TESTING</TextBlock>
</Grid>
</UserControl>
In MainWindowViewModel:
private void onOpenTestView(object sender)
{
Window w = new Window();
w.Content = new TestViewModel();
w.Show();
}
Running the app results in a window with a "MyNamespace.TestViewModel" string, instead of "TESTING", which would infer my data template is not being found.
I am very new to all this so am I missing something obvious? I don't think it's a string matching issue since if I deliberately misspell the view or model in the XAML then it doesn't compile.
Should my new window be able to access my app resources (and thus my datatemplate) ok?
Cheers,
Jeremy
EDIT: FIXED (Can't answer for 8 hours)
Due to MS Bug where resources not being read unless at least one style is set.
See http://connect.microsoft.com/VisualStudio/feedback/details/553528/defaultstylekey-style-not-found-in-inner-mergeddictionaries

Due to MS Bug where resources not being read unless at least one style is set.
See http://connect.microsoft.com/VisualStudio/feedback/details/553528/defaultstylekey-style-not-found-in-inner-mergeddictionaries

Related

MAUI Community Toolkit EventToCommandBehavior

I'm desperately trying to adhere to the MVVM design pattern in my App. So I'm trying to use the EventToCommandBehavior behavior from the MCT. (I'm also using the CommunityToolkit.Mvvm for [RelayCommand]) I've attached it to an Entry and am trying to forward the TextChanged event to my command. However, my command doesn't execute.
XAML:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Class="MyApp.View.Accounts.AddAccountPage"
xmlns:viewmodel="clr-namespace:MyApp.ViewModel.AccountsViewModel"
Title="Add Account">
<VerticalStackLayout>
<Entry x:Name="entryAccountName"
Placeholder="Account Name"
PlaceholderColor="Black"
TextColor="{StaticResource Tertiary}"
BackgroundColor="{StaticResource Primary}"
WidthRequest="125"
HorizontalOptions="Center"
Keyboard="Text"
ClearButtonVisibility="WhileEditing"
ReturnType="Next">
<Entry.Behaviors>
<toolkit:EventToCommandBehavior
EventName="TextChanged"
Command="{Binding AccountTextChangedCommand}" />
</Entry.Behaviors>
</Entry>
....More XAML....
AccountsViewModel code:
[RelayCommand]
public void AccountTextChanged()
{
Application.Current.MainPage.DisplayAlert("Text changed", "Account Text Changed", "OK");
}
I've got a breakpoint set to the Method and it just never gets called. Any ideas as to what am I doing wrong?
If by "reference in the header", you mean the line xmlns:viewmodel=...,
all that can do is declare a namespace that can be used in the following xaml. It can't refer to a class in that namespace - the line you show is ignored because it is not a valid namespace - you need:
<ContentPage
xmlns:viewmodel="clr-namespace:MyApp.ViewModel" <--- NAMESPACE, NOT CLASS!
/>
<ContentPage.BindingContext>
<viewmodel:AccountsViewModel/>
</ContentPage.BindingContext>
lines in your xaml, to say which viewmodel is the binding context. This is equivalent to the c# code you put in constructor.
OR There is an alternative technique, using Dependency Injection.
In MauiProgram.CreateMauiApp(), add lines similar to:
mauiAppBuilder.Services.AddTransient<MyApp.ViewModel.AccountsViewModel>();
mauiAppBuilder.Services.AddTransient<MyApp.View.Accounts.AddAccountPage>();
Exact details of those lines depend on your existing CreateMauiApp code, and your namespaces.
I finally figured it out. I had forgotten to set the BindingContext to the AccountsViewModel in the constructor of the page:
using MyApp.Services;
using MyApp.ViewModel.AccountsViewModel;
namespace MyApp.View.Accounts;
public partial class AddAccountPage : ContentPage
{
public AddAccountPage()
{
InitializeComponent();
DataService dataService = new();
AccountsViewModel accountsViewModel = new(dataService);
BindingContext = accountsViewModel;
}
}
I've been having to set the BindingContext via constructors on various pages to get the data bindings to work, even though I reference the ViewModels in the XAML header. Is this intended or am I going about it wrong?

.NET MAUI TableView collapses when Entry control receives focus

I have a TableView in which I render form controls for data editing.
However, as soon as the Entry control receives focus, the ViewCell seemingly collapses, leaving only the section title and separator borders visible:
<TableView Intent="Data">
<TableRoot>
<TableSection Title="Details">
<ViewCell>
<Grid ColumnDefinitions="0.5*,0.5*">
<Label Text="Manufacturer" />
<Entry Text="{Binding Manufacturer}" Grid.Column="1" />
</Grid>
</ViewCell>
</TableSection>
</TableRoot>
</TableView>
Initial state:
After tapping the Entry element:
I've tried setting a specific height for the Grid and the Entry control, but I get the same result regardless.
Am I missing something obvious here? 🤔
It's a bug in .NET MAUI: https://github.com/dotnet/maui/issues/10322
I ran into this a while ago and reported it. For now, I have created my own table using a Grid.
You can change The TableView HeightRequest property to a large number such as 700.
This is a temporary workaround until the bug is fixed.
<TableView HeightRequest="700">
<TableRoot>
<TableSection>
<!-- Your code goes here -->
</TableSection>
</TableRoot>
</TableView>
But, in most scenarios, you don't know what the Height is so you could set it dynamically in your code behind.
Page constructor:
public MainPage()
{
InitializeComponent();
// if you want your table height to fit half of the page
Table.HeightRequest = this.Height / 2;
// Rest of logic...
}
Xaml:
<TableView x:Name="Table">
<TableRoot>
<TableSection>
<ViewCell>
<Label Text="Example"/>
</ViewCell>
</TableSection>
</TableRoot>
</TableView>

Xamarin Prism binding same command in multiple views

I've a page view which has TabView and each tabview item is a separate view, containing list.
<TabView>
<TabViewItem HeaderText="Comments">
<TabViewItem.Content>
<local:CommentsListView/>
</TabViewItem.Content>
</TabViewItem>
<TabViewItem HeaderText="Stock">
<TabViewItem.Content>
<local:StockListView/>
</TabViewItem.Content>
</TabViewItem>
<TabViewItem HeaderText="Labour">
<TabViewItem.Content>
<local:LabourListView/>
</TabViewItem.Content>
</TabViewItem>
</TabView>
In each child view I've enabled pulldown and pulldown command in each view is pointing to the same 'RefreshCommand'. I want RefreshCommand to be called once no matter from which view pulldown was performed.
Problem is that if I have say 5 child views then refresh command is called 5 times. Obviously I could bind 5 different commands which calls the same method, but would be nice if I could reuse same command.
Is this possible?
Edit: added sample of view in each tab. They all are the same except pointing to different ItemSource:
<?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:prism="http://prismlibrary.com"
prism:ViewModelLocator.AutowireViewModel="True"
xmlns:custom="clr-namespace:Controls"
xmlns:converter="clr-namespace:Converters"
x:Class="CommentsListView">
<ContentView.Resources>
<ResourceDictionary>
<converter:NegateBooleanConverter x:Key="inverter" />
</ResourceDictionary>
</ContentView.Resources>
<custom:ObjectGridView
x:Name="commentsListView"
ItemsSource="{Binding CommentsList}"
IsPullToRefreshEnabled="{Binding InEditMode, Converter={StaticResource inverter}}"
IsRefreshing="{Binding IsRefreshing, Mode=TwoWay}"
PullToRefreshCommand="{Binding RefreshCommand}"
RowTapCommand="{Binding CommentTapCommand}"
ShowAddButton="True"
AddButtonVisible="{Binding InEditMode}"
AddButtonCommand="{Binding AddButtonCommand}"/>
</ContentView>

ZK how to add grid based on user input

I'm working in a web application using ZK. I'm struggling with a window where I need to render a grid with as much rows as the user specifies. It should be something like:
<grid>
<columns>
<column width="150px"/>
<column width="350px"/>
</columns>
<rows>
<row>
<cell>
<label value="Rows to add"/>
</cell>
<cell>
<textbox id="txt_rowamount"/>
</cell>
</row>
</rows>
</grid>
<grid id="grid">
<columns>
<column width="100px" label="Field 1" align="center"/>
<column width="100px" label="Field 2" align="center"/>
<column width="100px" label="Field 3" align="center"/>
</columns>
<rows id="rows" forEach="${rowModelList}">
<row>
<cell>
<textbox value="${each.field1}" />
</cell>
<cell>
<textbox value="${each.field2}" />
</cell>
<cell>
<textbox value="${each.field3}" />
</cell>
</row>
</rows>
</grid>
<button id="btn_generate" label="Generate Rows"/>
So, if you type 4 in txt_rowamount a grid with 4 row components and 3 textbox components in each row. The textboxes should be empty and when I complete every textbox the user input should be binded to fieldN of every rowModel item, do I make myself clear?
I'm trying with something like this in the composer:
private ListModel<RowModel> rowModelList;
#Autowired
private Grid grid;
#Override
public void doAfterCompose(final Window component) throws Exception {
super.doAfterCompose(component);
rowModelList = new ListModelList<>();
grid.setVisible(false);
}
public void onClick$btn_generate() {
// TODO Add four empty elements to rowModelList
grid.setVisible(true);
}
Being RowModel like
public class RowModel {
private String field1;
private String field2;
private String field3;
// ommited constructors, getters and setters
}
I believe that the approach should be MVVM instead of the current MVC but I don't know how to do this. Besides, I don't know if you can mix the approachs in the same application and what are the advantages and disadvantages of doing that.
I've seeing the ZK grid demos but all of them are using already populated objects to render the tables, so it's not useful to me.
If someone can give me a hand in this issue, it would be greatly appreciated. If you need more information on the code or the requirements for this problem, please comment it out.
Thanks in advance for your answers.
It seems there's a lot missing in your current attempt, so I'll try to fill the gaps.
If impatient here a runnable example on zkfiddle.
Since you were talking about binding component attributes to model values I'd suggest using ZK's DataBinding which integrates nicely into the MVVM development pattern (sorry for the link, but that's not a one-liner).
In any case the example illustrates how to combine ZK features such as (ListModelList with templates, Property-/Command-Binding) into a simple dynamic UI.
As you can see in code, adding, (re)moving a row inside the model requires rudimentary operations (the grid will update its child-rows automatically):
rows.add(new RowModel(...));
rows.remove(row);
Since the ListModelList is assigned to the <grid> component via #init(vm.rows), the grid will listen to changes to the ListModel and add remove grid-rows accordingly. It uses the <template name="model" var="rowModel"> to render new rows when added to the model. The template variable rowModel variable represents the rowModel object. (more on that in our documentation)
I hope this information is not overwhelming ... if so please let me know.
BTW: the forEach-attribute you were trying to use previously is used for static ui composition (at zul parse time) where no dynamic control is required. For dynamic cases use ListModel(List) in combination with templates.
Robert

The property 'IsDataSource' was not found in type 'ViewModelLocator

I have the following code:
<UserControl x:Class="TestApp.View.ViewAlarmLog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:Custom="http://schemas.microsoft.com/wpf/2008/toolkit"
xmlns:mvvm="clr-namespace:Test.ViewModel">
<UserControl.Resources>
<ResourceDictionary >
<ResourceDictionary.MergedDictionaries>
</ResourceDictionary.MergedDictionaries>
<mvvm:ViewModelLocator x:Key="Locator" d:IsDataSource="True"/>
</ResourceDictionary>
</UserControl.Resources>
the problem is dat i always get an error:
The property 'IsDataSource' was not found in type 'ViewModelLocator'. "
What could be the problem here ?
Redgards
Dieter
I think the IsDataSource attribute is specific to Expression Blend. Other editors, like Visual Studio, won't be able to understand it.
You can fix this problem by instructing the editor to ignore this attribute.
Try adding the following attributes to the UserControl element:
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"