maui
maui copied to clipboard
SwipeView throw exception on windows
Description
it seems following combintions triggers program to throw an exception of "Value does not fall within the expected range.":
- CollectioView with SwipeView
- the binding list of collectionview is initiazed with some items
- then clear the list, and add a new item
I have created a simple example to reproduce: https://github.com/hujun-open/mauiswieviewissue
I only found this issue on windows, android works fine.
following are the visual studio version info:
Microsoft Visual Studio Community 2022
Version 17.3.0 Preview 4.0
VisualStudio.17.Preview/17.3.0-pre.4.0+32714.290
Microsoft .NET Framework
Version 4.8.04084
Installed Version: Community
ASP.NET and Web Tools 17.3.372.35679
ASP.NET and Web Tools
Azure App Service Tools v3.0.0 17.3.372.35679
Azure App Service Tools v3.0.0
C# Tools 4.3.0-3.22329.30+29e657c0582904529bae2a87c227220e03f509cf
C# components used in the IDE. Depending on your project type and settings, a different version of the compiler may be used.
Common Azure Tools 1.10
Provides common services for use by Azure Mobile Services and Microsoft Azure Tools.
Extensibility Message Bus 1.2.6 (master@34d6af2)
Provides common messaging-based MEF services for loosely coupled Visual Studio extension components communication and integration.
Microsoft JVM Debugger 1.0
Provides support for connecting the Visual Studio debugger to JDWP compatible Java Virtual Machines
Mono Debugging for Visual Studio 17.3.20 (3f4cb00)
Support for debugging Mono processes with Visual Studio.
NuGet Package Manager 6.3.0
NuGet Package Manager in Visual Studio. For more information about NuGet, visit https://docs.nuget.org/
Razor (ASP.NET Core) 17.0.0.2232702+e1d654e792aa2fe6646a6935bcca80ff0aff4387
Provides languages services for ASP.NET Core Razor.
TypeScript Tools 17.0.10701.2001
TypeScript Tools for Microsoft Visual Studio
Visual Basic Tools 4.3.0-3.22329.30+29e657c0582904529bae2a87c227220e03f509cf
Visual Basic components used in the IDE. Depending on your project type and settings, a different version of the compiler may be used.
Visual F# Tools 17.1.0-beta.22327.2+ddc90b20287a765a9d526da42b3be0dd8e907ec5
Microsoft Visual F# Tools
Visual Studio IntelliCode 2.2
AI-assisted development for Visual Studio.
VisualStudio.DeviceLog 1.0
Information about my package
VisualStudio.Mac 1.0
Mac Extension for Visual Studio
VSPackage Extension 1.0
VSPackage Visual Studio Extension Detailed Info
Xamarin 17.3.0.288 (main@5d42bb2)
Visual Studio extension to enable development for Xamarin.iOS and Xamarin.Android.
Xamarin Designer 17.3.0.204 (remotes/origin/d17-3@f5da0100a)
Visual Studio extension to enable Xamarin Designer tools in Visual Studio.
Xamarin.Android SDK 13.0.0.0 (d17-3/030cd63)
Xamarin.Android Reference Assemblies and MSBuild support.
Mono: dffa5ab
Java.Interop: xamarin/java.interop/d17-3@7716ae53
SQLite: xamarin/sqlite/3.38.5@df4deab
Xamarin.Android Tools: xamarin/xamarin-android-tools/main@14076a6
Steps to Reproduce
see readme @ https://github.com/hujun-open/mauiswieviewissue
Version with bug
6.0.400
Last version that worked well
Unknown/Other
Affected platforms
Windows
Affected platform versions
windows 10 Enterprise 21H2, OS build 19044.1826
Did you find any workaround?
no
Relevant log output
+ sender {SwipeIssueDemo.WinUI.App} object {SwipeIssueDemo.WinUI.App}
- e {Microsoft.UI.Xaml.UnhandledExceptionEventArgs} Microsoft.UI.Xaml.UnhandledExceptionEventArgs
- Exception {"Value does not fall within the expected range."} System.Exception {System.ArgumentException}
+ Data {System.Collections.ListDictionaryInternal} System.Collections.IDictionary {System.Collections.ListDictionaryInternal}
HResult -2147024809 int
HelpLink null string
+ InnerException null System.Exception
Message "Value does not fall within the expected range." string
ParamName null string
Source null string
StackTrace null string
TargetSite null System.Reflection.MethodBase
+ Static members
+ Non-Public members
Handled false bool
Message "The parameter is incorrect.\r\n" string
+ Non-Public members
Depends on
- [ ] https://github.com/microsoft/microsoft-ui-xaml/issues/2527
VS bug #1870771
The native exception from WinUI:
onecoreuap\windows\dwm\dcomp\winrtnested\wrtcompositioninteractionsource.cpp(109)\dcompi.dll!00007FF806E93531: (caller: 00007FF806E9676F) ReturnHr(1) tid(6684) 80070057 The parameter is incorrect.
Msg:[PointerEventRouter (Interaction) object already has an owner.]
I'm seeing a similar issue in my simple MAUI application with an ObservableCollection[Microsoft.UI.Xaml.UnhandledExceptionEventArgs] Exception = {"Value does not fall within the expected range."} Message = {"The parameter is incorrect."}
I also have defensive code in each add/remove method to make sure the collection is not null and the item has content before removing or adding.
[UPDATE] I just discovered the issue was with the SwipeView inside the CollectionView. I hope someone on the MAUI team is working on the bugs with the SwipeView control. I have taken it out for now; not sure how a developer would make this work on a phone?... for now I'm just going to show a delete button.
<CollectionView
Grid.Row="2"
Grid.ColumnSpan="2"
ItemsSource="{Binding Items}"
SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="{x:Type x:String}">
<!-- [I had to remove the SwipeView because of it's buggyness and the UnhandledException: "Value does not fall within the expected range."]
<SwipeView>
<SwipeView.RightItems>
<SwipeItems>
<SwipeItem
BackgroundColor="Red"
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:MainViewModel}}, Path=DeleteCommand}"
CommandParameter="{Binding .}"
Text="Delete" />
</SwipeItems>
</SwipeView.RightItems>
-->
<Grid Padding="0,5" ColumnDefinitions=".9*, .1*">
<Frame Grid.Column="0" HasShadow="True">
<Label
Grid.Row="0"
Margin="0"
Padding="0"
FontSize="24"
Text="{Binding .}"
VerticalOptions="Start" />
</Frame>
<Button
Grid.Column="1"
Margin="3,0,0,0"
Padding="24"
BackgroundColor="Transparent"
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:MainViewModel}}, Path=DeleteCommand}"
CommandParameter="{Binding .}" />
</Grid>
<!--
</SwipeView>
-->
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
The binding source "Items" is an ObservableCollection{string} inside the ViewModel. You can uncomment the SwipeView to see the exception happen.
Are there any recommended workarounds for this? There's no conditional compilation in XAML, so the only other thing I can think of is having a separate page / view for Windows vs other platforms.
Thats what I have had to do for now Separate shells for windows vs everything else.
The issue is possible to reproduce using a Delete Button
///ViewModels.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Windows.Input;
namespace Inventory.ViewModels
{
public partial class MainViewModel : ObservableObject
{
public MainViewModel() {
items = new ObservableCollection<string>();
text = string.Empty;
}
[ObservableProperty]
ObservableCollection<string> items;
[ObservableProperty]
string text;
[RelayCommand]
void Add() {
if (string.IsNullOrEmpty(text?.Trim()))
{
return;
}
Items.Add(text);
Text = string.Empty;
}
[RelayCommand]
void Delete(string value)
{
if (string.IsNullOrEmpty(value?.Trim()))
{
return;
}
if (Items.Any(s => s.Equals(value)))
{
Items.Remove(value);
}
}
}
}
//MainPage.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"
x:Class="Inventory.MainPage"
xmlns:viewmodel="clr-namespace:Inventory.ViewModels"
x:DataType="viewmodel:MainViewModel">
<Grid RowDefinitions="100, auto, *" ColumnDefinitions=".75*, .25*" Padding="10" RowSpacing="10" ColumnSpacing="10">
<Image Grid.ColumnSpan="2" Source="dotnet_bot.png" BackgroundColor="Orange"></Image>
<Entry Placeholder="Enter the task Nanme" Grid.Row="1" Text="{Binding Text}"></Entry>
<Button Text="Add" Grid.Row="1" Grid.Column="1" Command="{Binding AddCommand}"></Button>
<CollectionView Grid.Row="2" Grid.ColumnSpan="2" ItemsSource="{Binding Items}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="{x:Type x:String}">
<SwipeView>
<SwipeView.RightItems>
<SwipeItems>
<SwipeItem Text="Delete" BackgroundColor="Red"></SwipeItem>
</SwipeItems>
</SwipeView.RightItems>
<Grid RowDefinitions="auto" ColumnDefinitions="*, auto">
<Frame BackgroundColor="Navy">
<Label Text="{Binding .}" FontSize="24" Grid.Column="1"></Label>
</Frame>
<Button Text="Delete" BackgroundColor="Red" Grid.Column="2"
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:MainViewModel}}, Path=DeleteCommand}"
CommandParameter="{Binding .}"/>
</Grid>
</SwipeView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
</ContentPage>
//MainPage
public partial class MainPage : ContentPage
{
public MainPage( MainViewModel vm)
{
InitializeComponent();
BindingContext = vm;
}
}
//MauiProgram.cs
using Inventory.ViewModels;
namespace Inventory;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
builder.Services.AddSingleton<MainPage>();
builder.Services.AddSingleton<MainViewModel>();
return builder.Build();
}
}
UPDATE 2: I still have the issue with VS 2022 17.4.5.
UPDATE: ~~My issue seems to be fixed with VS 2022 17.3.6.~~
I get the same exception now on Windows.
It worked with VS 2022 17.3.2. After upgrading to VS 2022 17.3.5 this issue occurred in my App as well on Windows. But it works on Android 12.
Reproduce:
- Clone: https://github.com/jbe2277/waf/tree/904ae96984603d982eaa85bd6a411bc89717bbac
- Open src\NewsReader\NewsReader.sln
Try out:
- Remove SwipeView: https://github.com/jbe2277/waf/blob/904ae96984603d982eaa85bd6a411bc89717bbac/src/NewsReader/NewsReader.Presentation/Views/FeedView.xaml#L36
- Without the SwipeView this crash does not occur.
Hi @hujun-open. We have added the "s/needs-info" label to this issue, which indicates that we have an open question for you before we can take further action. This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.
Is this fixed? Can we close?
@mattleibow I'm curious, how @hujun-open would be expected to know if it is fixed, is the merged PR available somewhere? I submitted issue #9423 which was closed as a duplicate of this issue, as well as #9233 which is probably fixed by the same PR, so I'd like a fix too but I'm not sure how to go about discovering if this is it until the PR changes are released. Did you have some mechanism in mind?
I ran into this issue about two days ago, right after upgrading to .NET 7 release. So it's definitely unsolved.
I moved away from MAUI for now, no longer have the setup, but there is repo to reproduce the issue, anyone could use that to see if the issue is fixed
I just wasted an hour or so figuring out this crash was the swipeview, so still a problem on 17.4.1
Same problem here. Quite annoying to hit so many bugs actually. Is this being looked at least? My workaround for the moment is to instantiate a new ObservableCollection and (since that breaks change notifications) triggering the change notification manually, like so:
#if WINDOWS
// only necessary because of: https://github.com/dotnet/maui/issues/8870
DocumentLines = new();
#else
DocumentLines.Clear();
#endif
foreach (var l in hiddenDocumentLines.OrderBy(l => l.VisualOrder))
DocumentLines.Add(l);
hiddenDocumentLines.Clear();
#if WINDOWS
// only necessary because of: https://github.com/dotnet/maui/issues/8870
OnPropertyChanged(nameof(DocumentLines));
#endif
Does anyone have a better workaround than this?
This worked for me, but I had to use if (DeviceInfo.Current.Platform == DevicePlatform.WinUI)
We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.
I don't understand how this is being put off again. I guess no one is using MAUI for simultaneous desktop and mobile deployments? Maybe no one is using SwipeView? 🤔
Since this was used to close at least #9540, #9423 and #8870 there seems to be a fair amount of interest. PR #11229 seems like it corrects this but says "The PR require some changes in the public API. Will maintain the changes up to date but need to wait to .NET 8."
Fair enough, but if breaking changes are the problem at least put something in the community toolkit temporarily because waiting for .NET 8 to fix a bug in .NET 6 seems excessive.
Long live to xamarin form them.
On Fri, Feb 3, 2023 at 2:07 PM David Maw @.***> wrote:
Since this was used to close at least #9540 https://github.com/dotnet/maui/issues/9540, #9423 https://github.com/dotnet/maui/issues/9423 and #8870 https://github.com/dotnet/maui/issues/8870 there seems to be a fair amount of interest. PR #11229 https://github.com/dotnet/maui/pull/11229 seems like it corrects this but says "The PR require some changes in the public API. Will maintain the changes up to date but need to wait to .NET 8."
Fair enough, but if breaking changes are the problem at least put something in the community toolkit temporarily because waiting for .NET 8 to fix a bug in .NET 6 seems excessive.
— Reply to this email directly, view it on GitHub https://github.com/dotnet/maui/issues/8870#issuecomment-1416348243, or unsubscribe https://github.com/notifications/unsubscribe-auth/AFZB5A7QIOGUNCHBZJPHAWTWVVQPZANCNFSM54FRA3VQ . You are receiving this because you commented.Message ID: @.***>
Seems like there's no love for desktop apps in the new tech. The lack of a mouse-over event in MAUI was proof of this. I'm just wondering how this was missed or not tested properly? Usually you test features in a desktop/native build before porting to mobile during the dev cycle.
This issue still exists with Visual Studio 17.4.5 and MAUI 7.0.59.
How to reproduce:
- Create a new MAUI project with .NET 7
- Replace content of
MainPage.xaml
with
<?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"
x:Class="MauiApp1.MainPage">
<Grid RowDefinitions="Auto,*" Margin="10" RowSpacing="10">
<Button Text="Insert items" Clicked="InsertItemsClicked" HorizontalOptions="Start"/>
<CollectionView Grid.Row="1" ItemsSource="{Binding Items}">
<CollectionView.ItemTemplate>
<DataTemplate>
<SwipeView>
<Grid>
<Border Stroke="Blue" StrokeThickness="2">
<Label Text="{Binding}" Padding="5"/>
</Border>
</Grid>
</SwipeView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
</ContentPage>
- Replace content of
MainPage.xaml.cs
with
using System.Collections.ObjectModel;
namespace MauiApp1;
public partial class MainPage : ContentPage
{
int counter = 1;
public MainPage()
{
InitializeComponent();
Items = new();
BindingContext = this;
}
public ObservableCollection<string> Items { get; }
private void InsertItemsClicked(object sender, EventArgs e)
{
const int numberOfEntries = 5;
for (int i = 0; i < numberOfEntries; i++) { Items.Insert(0, $"Item {counter}"); counter++; }
}
}
- Press
Insert items
button until the items do not fit in the view anymore and would need a scrollbar -> Exception occurs. See screen cast:
Note: Remove the SwipeView
from the DataTemplate
. Then this error does not occur.
I just ran into the same problem with Visual Studio 17.4.5 and MAUI 7.0.59 in a demo project I'm developing for a conference where I'll be talking about .NET MAUI and wanted to share my workaround.
I was able to get around the issue by migrating my XAML code to C# markup using the helper methods from the [.NET MAUI Community Toolkit] (https://learn.microsoft.com/de-de/dotnet/communitytoolkit/maui/markup/markup). Within the C# markup file, I was able to use conditional compilation to exclude the SwipeView on Windows.
See:
https://github.com/AndreKraemer/basta-2023-se-maui-demo/blob/fb33963810b7788795fcf6445d4d5f4cf256e26b/ConferenceMauiDemo/Views/SessionsPageMarkup.cs#L152
Since the SwipeView
doesn't work on Windows when using the application with a mouse, it wasn't a big deal to exclude it from my view. To give Windows users the same functionality that users of other platforms have with the SwipeView
, I added a context menu (right click). See:
https://github.com/AndreKraemer/basta-2023-se-maui-demo/blob/fb33963810b7788795fcf6445d4d5f4cf256e26b/ConferenceMauiDemo/Views/SessionsPageMarkup.cs#L238
Within the C# markup file, I was able to use conditional compilation to exclude the SwipeView on Windows.
Was literally just thinking of this today. Thanks for testing it out and letting us know!
I ran into the same problem, but not with swipeview i create a collectionview and binding with Multiple selection mode
<CollectionView x:Name="collectionView"
ItemsSource="{Binding NoteSegments}"
SelectedItems="{Binding SelectedNoteSegments}"
SelectedItem="{Binding SelectedNoteSegment}"
SelectionMode="Multiple"
ItemTemplate="{StaticResource NoteSegmentDataTemplateSelector}" />
After the page is loaded, I delete several selected items ,then
private void RemoveSelectedSegmentAction(object obj)
{
foreach (var noteSegments in SelectedNoteSegments.ToList())
{
NoteSegments.Remove((INoteSegmentService)noteSegments);
}
}
Then add some items
private async void EditNotePageViewModel_OnFinishedChooise(object sender, NoteSegment noteSegment)
{
noteSegment.NoteId = this.NoteId;
noteSegment.NoteSegmentPayloads = new List<NoteSegmentPayload>();
var newModel = noteSegmentServiceFactory.GetNoteSegmentService(noteSegment);
if (newModel != null)
{
newModel.Create.Execute(null);
this.NoteSegments.Add(newModel);
}
(sender as NoteSegmentStoreListPageViewModel).OnFinishedChooise -= EditNotePageViewModel_OnFinishedChooise;
await navigationService.HidePopupAsync(noteSegmentStoreListPage);
}
private async void CreateSegmentFromStoreAction(object obj)
{
using (var objWrapper = iocResolver.ResolveAsDisposable<NoteSegmentStoreListPage>())
{
noteSegmentStoreListPage = objWrapper.Object;
(noteSegmentStoreListPage.BindingContext as NoteSegmentStoreListPageViewModel).OnFinishedChooise += EditNotePageViewModel_OnFinishedChooise;
await navigationService.ShowPopupAsync(noteSegmentStoreListPage);
}
}
And here's the problem
I'm using .net8 preview 5 and the problem still exists for me, my project is using windows and mac desktop, and iOS and Android. Working fine in all but Windows where it crashes because I have a swipe view inside a collection view.
Is this being worked on for .net8 ?
Absent a response from Microsoft I can only guess, but it looks like it's being worked on. I noticed a while back that PR #11229 by @jsuarezruiz seems like it corrects this but says "The PR require some changes in the public API. Will maintain the changes up to date but need to wait to .NET 8." That was in November of 2022, however it looks like a new decision has been reached and PR #11229 was closed at the beginning of July 2023 with "We are going to close this PR focusing on fix crashes or any problem that prevents the use of SwipeView in Windows while we continue to promote the implementations and accessibility on the desktop (keyboard support etc)."
A SwipeView without mouse support is not a very practical desktop UI element but as long as its presence does not crash the app (as it currently does in this example) it shouldn't be too hard to code around that and offer an alternative desktop interface. Not hard, but tedious.
I agree, I have a swipe view for use on mobile devices and am using context menus for desktop. At a minimum we need the presence of a swipe view in a collection view to at least not crash!
Could be related with: https://github.com/microsoft/microsoft-ui-xaml/issues/2527
Can reproduce the issue even with the changes:
- SwipeControl without SwipeItems.
- SwipeControl wrapped by a Container.
- Setting default initial size, delay measuring etc.
I don't know if this will help the majority of projects encountering this problem, but I have found an All-XAML workaround to this problem. This should work with any control that uses a DataTemplate, but, as I am fairly new to .NET MAUI / Xamarin, I don't know how far this workaround will extend to everyone's issues. I do hope that this helps someone, though.
I have broken this long post into two parts, the background for my issue (in case some of you are facing something similar, or where this information may be useful to someone). And, a second section called 'The Good Stuff'. If you just want to see the solution that I used, skip the first section (it may be a waste of your time to read it anyway). 😃
Background for My Issue
I was encountering this issue when I deleted an item in my ViewModel's ObservableCollection
(which is bound to a CollectionView
in my main view's XAML) and then added a new item during the same session. On Windows it would crash the application, but it worked fine on Android (I didn't test it on iOS or MacOS). This crash occurred during the Shell.Current.GoToAsync()
method, which I am using to pass an object back to the main view's VM, using the ApplyQueryAttributes()
method to capture the passed in data. All of the application's data is being stored to a SQLite database, which was important to me in figuring out that it wasn't just a null
value of some sort causing a crash.
Execution would follow as one would expect, by entering the command I have defined to create the data object (when the 'Create' button is pressed) and call Shell.Current.GoToAsync()
to go back to the main view, passing the data object. The data object is valid; my immediate thought was that somehow it may be null
when I encountered the error, but it contained all the necessary data. The execution then jumped from GoToAsync()
to the VM's ApplyQueryAttributes()
method where it correctly processed the data object by adding it to my ObservableCollection
and storing it in the database. Good, no issues. This is where things seem strange to me, because then execution jumped back to the GoToAsync()
method again, even though I have await
-ed the GoToAsync()
method. And this point in the execution is where the application would crash. To be clear, the data object is added to the ObservableCollection
and to the database successfully. After the crash, I can start the app up again and the added object (which caused the crash) will be in the CollectionView
...so, it was added to the database successfully, and adding the object to the ObservbleCollection
is not where the app crashed. It was only when execution jumped back to GoToAsync()
that things went sideways.
Again, I am new to .NET MAUI and to be completely honest, I am fairly new to C# (although I have been programming in C/C++ for many years). I was under the (clearly misguided) belief that the application would wait for the GoToAsync()
method's execution to complete before jumping to the ApplyQueryAttributes()
method in my ViewModel since the ApplyQueryAttributes()
method needs the information being passed through GoToAsync()
and that the main thread should have to halt for that information to be passed. To me, this seems strange that it would begin executing the GoToAsync()
method, jump to the ApplyQueryAttributes()
method, and then jump back to the GoToAsync()
method. Meanwhile, all of the data being passed through the GoToAsync()
method is valid, it is received correctly in my VM and is fully processed, and then the execution of GoToAsync()
completes. 🤷
I am only including all of this information for completeness. I don't know how much of this actually applies to the underlying issue with the SwipeView
control, of if it matters at all.
Now, on to the solution that worked for me.
The Good Stuff
I am an inherently lazy person, and since I intent to make all the source code to my app open source, I am just going to copy-paste what I have to fix this problem.
TopicCard View XAML
<?xml version="1.0" encoding="utf-8" ?>
<Frame xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:CornellPad"
xmlns:viewmodel="clr-namespace:CornellPad.ViewModels"
xmlns:entry="clr-namespace:CornellPad.Models"
x:DataType="entry:TopicModel"
x:Class="CornellPad.Views.Cards.TopicCard"
CornerRadius="20"
Margin="10,10, 15, 0"
BorderColor="{AppThemeBinding Light={StaticResource Blue200Accent}, Dark={StaticResource Gray400}}">
<Frame.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding GoToTopicViewCommand, Source={RelativeSource AncestorType={x:Type viewmodel:LibraryViewModel}}}"
CommandParameter="{Binding .}"/>
</Frame.GestureRecognizers>
<Grid ColumnDefinitions="120, Auto"
RowDefinitions="130"
Margin="5">
<Image Grid.Column="0"
Source="{Binding Icon}"
HeightRequest="100"
WidthRequest="100"/>
<VerticalStackLayout Grid.Column="1"
Padding="5">
<Border
Padding="10, 6"
Stroke="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource Gray500}}"
StrokeThickness="3"
StrokeShape="RoundRectangle 0,10,10,0"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Start"
BackgroundColor="{AppThemeBinding Light={StaticResource Blue300Accent}, Dark={StaticResource Gray900}}">
<Label
Text="{Binding TopicName}"
Padding="5"
FontSize="{OnPlatform Default=Subtitle, Android=Small}"/>
</Border>
<Label
Text="{Binding NumberOfNotes, StringFormat='{0} Notes in Topic'}"
Padding="10"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Start"/>
</VerticalStackLayout>
</Grid>
</Frame>
There is nothing special about this. Literally the only changed that I had to make in the code-behind was changing the parent class to Frame
instead of ContentView
. I just copied the XAML from my main view and pasted it in here, with a few alterations to wire everything up, of course.
LibraryView (my main view) 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"
xmlns:local="clr-namespace:CornellPad"
xmlns:viewmodel="clr-namespace:CornellPad.ViewModels"
xmlns:entry="clr-namespace:CornellPad.Models"
xmlns:cards="clr-namespace:CornellPad.Views.Cards"
x:DataType="viewmodel:LibraryViewModel"
x:Class="CornellPad.Views.LibraryView"
x:Name="LibraryViewPage"
Title="{Binding Title}">
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate
x:Key="WithDesktop"
x:DataType="entry:TopicModel">
<cards:TopicCard>
<FlyoutBase.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem
Text="Delete"
CommandParameter="{Binding .}"
Command="{Binding Source={x:Reference LibraryViewPage}, Path=BindingContext.DeleteTopicCommand}"/>
</MenuFlyout>
</FlyoutBase.ContextFlyout>
</cards:TopicCard>
</DataTemplate>
<DataTemplate
x:Key="WithoutDesktop"
x:DataType="entry:TopicModel">
<SwipeView
IsVisible="{OnPlatform WinUI=False, MacCatalyst=True, Android=True, iOS=True}">
<SwipeView.RightItems>
<SwipeItems>
<SwipeItemView
CommandParameter="{Binding .}"
Command="{Binding DeleteTopicCommand, Source={RelativeSource AncestorType={x:Type viewmodel:LibraryViewModel}}}">
<Frame
BackgroundColor="IndianRed"
WidthRequest="100"
HeightRequest="100"
VerticalOptions="Center"
CornerRadius="50"
Padding="0">
<Label
Text="Delete"
VerticalOptions="Center"
HorizontalOptions="Center"/>
</Frame>
</SwipeItemView>
</SwipeItems>
</SwipeView.RightItems>
<cards:TopicCard/>
</SwipeView>
</DataTemplate>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.ToolbarItems>
<ToolbarItem
Text="Add"
IconImageSource="{AppThemeBinding Light=add_light.png, Dark=add_dark.png}"
Command="{Binding GoToCreateTopicViewCommand}"/>
</ContentPage.ToolbarItems>
<CollectionView
ItemsSource="{Binding TopicEntries}"
ItemTemplate="{OnPlatform WinUI={StaticResource WithDesktop}, MacCatalyst={StaticResource WithDesktop}, Default={StaticResource WithoutDesktop}}"
SelectionMode="None">
</CollectionView>
</ContentPage>
By defining my DataTemplate
s as a resource with a key, I can then easily access them according to the platform I am compiling for. I didn't have to change any code-behind anywhere (with the one small exception mentioned above), nor did I have to change any of the code in my ViewModel. By breaking out the XAML markup into it's own separate 'card', I still have one source to define the UI/UX elements for that card. I was very concerned about having duplicate XAML markup, but this seems to circumvent that issue. [Looks around nervously]
As an aside, my xmlns:entry
should be named xmns:models
, which I plan to change. But as I said, I am inherently lazy and I haven't gotten around to that yet. So, you get the XAML markup with warts and all. 😛
Despite the issues that I have encountered, I have to say that I ❤️ .NET MAUI, even if we have to live with some issues. I hope this helps someone and that you all have a great day.
Thank you @Rabidgoalie for the awesome work-around! I didn't want to turn my template into a view, so I used a ControlTemplate instead and it works great! I was tired of having to maintain 2 templates (1 for swipe and 1 for desktop).
<ControlTemplate x:Key="ItemTemplate">
<Grid ColumnDefinitions="8,84,*,*"
RowDefinitions="auto,auto"
ColumnSpacing="8"
BackgroundColor="White">
<Image Grid.Column="1"
Grid.Row="0"
Grid.RowSpan="2"
VerticalOptions="Center"
HorizontalOptions="Center"
HeightRequest="36">
<Image.Source>
<FontImageSource Glyph="{x:Static icons:Icon.Tag}"
Color="Blue"
Size="36"
FontFamily="{x:Static icons:Font.IconSet}" />
</Image.Source>
</Image>
<VerticalStackLayout Grid.Column="2"
Grid.Row="0"
Margin="0,8"
Grid.RowSpan="2">
<Label FontSize="14"
Text="{Binding Name}"
LineBreakMode="TailTruncation"
VerticalOptions="Center" />
<Label FontSize="14"
Text="{Binding Info}"
LineBreakMode="TailTruncation"
Margin="0,5,0,5"
TextColor="Gray"
VerticalOptions="Center" />
</VerticalStackLayout>
<Label Grid.Column="3"
Grid.Row="0"
Grid.RowSpan="2"
Margin="0,0,12,0"
VerticalOptions="Center"
HorizontalOptions="End"
FontSize="16"
Text="{Binding Price, Converter={StaticResource CurrencyConverter}}"
TextColor="Purple" />
<BoxView Grid.Column="0"
Grid.ColumnSpan="4"
Grid.Row="1"
HeightRequest="1"
VerticalOptions="End"
Color="LightGray" />
</Grid>
</ControlTemplate>
<DataTemplate x:Key="MobileItemTemplate"
x:DataType="data:ItemModel">
<SwipeView>
<SwipeView.RightItems>
<SwipeItems>
<SwipeItem Text="Delete"
IsDestructive="True"
BackgroundColor="Red"
Command="{Binding RemoveCommand}"
CommandParameter="{Binding .}"
IsVisible="{Binding RemoveCommand, Converter={StaticResource NotNullConverter}}" />
</SwipeItems>
</SwipeView.RightItems>
<ContentView ControlTemplate="{StaticResource ItemTemplate}" />
</SwipeView>
</DataTemplate>
<DataTemplate x:Key="DesktopItemTemplate"
x:DataType="data:ItemModel">
<ContentView ControlTemplate="{StaticResource ItemTemplate}">
<FlyoutBase.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem Text="Remove"
Command="{Binding RemoveCommand}"
CommandParameter="{Binding .}"
IsEnabled="{Binding RemoveCommand, Converter={StaticResource NotNullConverter}}">
<MenuFlyoutItem.IconImageSource>
<FontImageSource Glyph="{x:Static icons:Icon.Remove}"
FontFamily="{x:Static icons:Font.IconSet}" />
</MenuFlyoutItem.IconImageSource>
</MenuFlyoutItem>
</MenuFlyout>
</FlyoutBase.ContextFlyout>
</ContentView>
</DataTemplate>