Custom Frame is not accepting Height Request in MAUI - frame

I am converting my Xamarin Forms Application to .NET MAUI.
I am trying to migrate the Custom renderer from Xamarin to MAUI following this link Using Custom Renderers in .NET MAUI
Here is my Code:
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseMauiCompatibility()
.ConfigureMauiHandlers(handlers =>
{
handlers.AddCompatibilityRenderer(typeof(CustomFrame), typeof(CustomShadowFrameRenderer));
});
return builder.Build();
}
CustomFrame:
public class CustomFrame : Frame
{
public CustomFrame()
{
}
}
Below is the Custom Renderer Class
public class CustomShadowFrameRenderer : FrameRenderer
{
public CustomShadowFrameRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
{
base.OnElementChanged(e);
if (e.NewElement != null && e.OldElement == null)
{
e.NewElement.HeightRequest = 1000;
e.NewElement.VerticalOptions = LayoutOptions.FillAndExpand;
}
}
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiApp1.MainPage" BackgroundColor="Red"
xmlns:local="clr-namespace:MauiApp1.Controls">
<local:CustomFrame BackgroundColor="Blue" HeightRequest="1000" Padding="20">
<VerticalStackLayout
Spacing="25"
Padding="30,0"
VerticalOptions="Center">
<Label
Text="Hello, World!"
SemanticProperties.HeadingLevel="Level1"
FontSize="32"
HorizontalOptions="Center" />
<Label
Text="Welcome to .NET Multi-platform App UI"
SemanticProperties.HeadingLevel="Level2"
SemanticProperties.Description="Welcome to dot net Multi platform App U I"
FontSize="18"
HorizontalOptions="Center" />
<Button
x:Name="CounterBtn"
Text="Click me"
SemanticProperties.Hint="Counts the number of times you click"
Clicked="OnCounterClicked"
HorizontalOptions="Center" />
</VerticalStackLayout>
</local:CustomFrame>
</ContentPage>
The Frame is not accepting the height Request in MAUI but the same works fine in Xamarin forms.
Below is the image for the same

I have create a sample to test your code and met the same problem. In addition, I even can't click the button in the CustomFrmae. It seems there are some compatibility issues about the FrameRender in the maui.
So I try to change the CustomRender to the CustomHandler. It worked well and you just need to change two place code.
In the CustomShadowFrameRenderer class, make it such as public class CustomShadowFrameRenderer : Microsoft.Maui.Controls.Handlers.Compatibility.FrameRenderer
In the MauiProgram.cs:
var builder = MauiApp.CreateBuilder();
            builder
             .UseMauiApp<App>()
.ConfigureMauiHandlers(handlers =>
{
                  handlers.AddHandler(typeof(CustomFrame), typeof(CustomShadowFrameRenderer));
             })

Related

WebView GestureRecognition not working in Xamarin forms

I have a webview When this webview is tapped I need to make visible of a button the problem Is Gesture recognition is not working
my Xaml
<customRenderer:CustomWebView Uri="{Binding SelectedJournal.Uri}" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand" x:Name="CustomWebView" AbsoluteLayout.LayoutBounds="0,50,1,1" AbsoluteLayout.LayoutFlags="SizeProportional" >
<customRenderer:CustomWebView.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_OnTapped2" NumberOfTapsRequired="1" ></TapGestureRecognizer>
</customRenderer:CustomWebView.GestureRecognizers>
</customRenderer:CustomWebView>
<customRenderer:NavigationImageButton ItemTapped="FullScreenOnTapped" Source="full.jpg" AbsoluteLayout.LayoutBounds="0.5,1,-1,-1" AbsoluteLayout.LayoutFlags="PositionProportional" HeightRequest="60" WidthRequest="60" x:Name="FullScreenBtn" IsVisible="False" >
In the code behind i called like this
private void TapGestureRecognizer_OnTapped2(object sender, EventArgs e)
{
FullScreenBtn.IsVisible = true;
}
This should work but this is not working
also my custom rendering web view class
public class CustomWebView : WebView
{
public static readonly BindableProperty UriProperty = BindableProperty.Create<CustomWebView, string>(p => p.Uri, default(string));
public string Uri
{
get { return (string)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
}
How to Achive this
Instead of using the gesture recognizer on your webview, you can use the 'Focused' event of your view to display your button. You can do something like this:
var wb = new WebView
{
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand,
Source = "https://stackoverflow.com/questions/56320611/webview-gesturerecognition-not-working-in-xamarin-forms",
};
wb.Focused += (s, e) =>
{
//Handle your logic here!
wb.Unfocus();
};
Here, Unfocus() is used if you wish to implement your logic everytime the webview is tapped.
A WebView is used in my application to display formatted text. Tapping the text should open an editor to modify the text. The suggested Focused event works on Android but not iOS (as of iOS 15.0 simulator at least). I found the solution in the comments for https://bugzilla.xamarin.com/42/42596/bug.html#c4. Adding an empty label to the xaml as a transparent overlay of the WebView registers the tap event and enables the gesture recogniser.
<Grid HeightRequest="132">
<WebView Grid.Column="0" Grid.Row="0"
Source="{Binding FormattedDescription, Converter={tr:StringToWebViewSourceConverter}}"
HeightRequest="132"/>
<Label Grid.Column="0" Grid.Row="0" Text=""
HeightRequest="132" HorizontalOptions="Fill">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding EditDescriptionCommand}"/>
</Label.GestureRecognizers>
</Label>
</Grid>

Problems with the interface when rotating the device horizontally

The interface of my app is divided into a header (it is a webview) and absolute content, which among other things contains a second webview.
In vertical orientation it works and looks perfect.
But when I turn the device horizontally the header stops measuring 70 and measures something like 30 ....
The header reduces the height although it has a fixed height.
Why this happen? Any solution?
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:PruebaHeight"
x:Class="PruebaHeight.MainPage">
<ContentPage.Content>
<StackLayout Spacing="0">
<WebView x:Name="webview_header" HorizontalOptions="FillAndExpand" HeightRequest="70" BackgroundColor="Red"/>
<AbsoluteLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" BackgroundColor="Black">
<!-- other things -->
<ScrollView AbsoluteLayout.LayoutBounds="0,0,1,1" AbsoluteLayout.LayoutFlags="All" BackgroundColor="Green">
<AbsoluteLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Padding="0" BackgroundColor="Purple">
<WebView x:Name="webview" AbsoluteLayout.LayoutBounds="0,0,1,1" AbsoluteLayout.LayoutFlags="All" BackgroundColor="Blue" />
</AbsoluteLayout>
</ScrollView>
</AbsoluteLayout>
</StackLayout>
</ContentPage.Content>
You could try to custom a view like following code.
XamWebView.cs
public class XamWebView: WebView
{
}
Then use it in your code.
var webview= new XamWebView();
webview.HorizontalOptions = LayoutOptions.Fill;
webview.VerticalOptions = LayoutOptions.StartAndExpand;
webview.WidthRequest = 70;
webview.HeightRequest = 70;
webview.Source = "https://www.google.com/";
Creating the Custom Renderer on Android(you could get the value of heightrequest when you running)
using WebView = Android.Webkit.WebView;
[assembly: ExportRenderer (typeof(XamWebView), typeof(XamWebViewRenderer))]
namespace Core.Droid
{
public class XamWebViewRenderer : WebViewRenderer
{
static XamWebView _xwebView = null;
WebView _webView;
public XamWebViewRenderer(Context context) : base(context)
{
}
class XamWebViewClient : Android.Webkit.WebViewClient
{
public override async void OnPageFinished(WebView view, string url)
{
if (_xwebView != null)
{
int i = 10;
while (view.ContentHeight == 0 && i-- > 0)
await System.Threading.Tasks.Task.Delay(100);
_xwebView.HeightRequest = view.ContentHeight;
}
}
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
_xwebView = e.NewElement as XamWebView;
_webView = Control;
if (e.OldElement == null)
{
_webView.SetWebViewClient(new XamWebViewClient());
}
}
}
}

Disable ScrollView in Xamarin Forms without disabling buttons

I have the following XAML. I want to target phones with a scrollview, and want scrolling disabled on a tablet.
<ScrollView InputTransparent="False" Orientation="Both" >
<ScrollView.IsEnabled>
<OnIdiom x:TypeArguments="x:Boolean">
<OnIdiom.Phone>True</OnIdiom.Phone>
<OnIdiom.Tablet>True</OnIdiom.Tablet>
</OnIdiom>
</ScrollView.IsEnabled>
<StackLayout VerticalOptions="FillAndExpand" BackgroundColor="White" >
<StackLayout.HorizontalOptions>
<OnIdiom x:TypeArguments="LayoutOptions">
<OnIdiom.Tablet>FillAndExpand</OnIdiom.Tablet>
<OnIdiom.Phone>Start</OnIdiom.Phone>
</OnIdiom>
</StackLayout.HorizontalOptions>
<Grid BackgroundColor="White" HeightRequest="65" MinimumHeightRequest="65">
<Grid.HorizontalOptions>
<OnIdiom x:TypeArguments="LayoutOptions">
<OnIdiom.Tablet>CenterAndExpand</OnIdiom.Tablet>
<OnIdiom.Phone>Start</OnIdiom.Phone>
</OnIdiom>
</Grid.HorizontalOptions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<WebView x:Name="webViewBtn1" HeightRequest="65" Grid.Column="0" Grid.ColumnSpan="1" VerticalOptions="FillAndExpand" BackgroundColor="White">
<WebView.HorizontalOptions>
<OnIdiom x:TypeArguments="LayoutOptions">
<OnIdiom.Tablet>CenterAndExpand</OnIdiom.Tablet>
<OnIdiom.Phone>Start</OnIdiom.Phone>
</OnIdiom>
</WebView.HorizontalOptions>
<WebView.WidthRequest>
<OnIdiom x:TypeArguments="x:Double">
<OnIdiom.Tablet>770</OnIdiom.Tablet>
<OnIdiom.Phone>300</OnIdiom.Phone>
</OnIdiom>
</WebView.WidthRequest>
</WebView>
<Button Grid.Column="0" Grid.ColumnSpan="1" x:Name="btn1" Clicked="btn1_Clicked" BackgroundColor="Transparent" TextColor="Transparent" BorderColor="White" />
</Grid>
</StackLayout>
</ScrollView>
the buttons no longer allow the user to click on them if I set ScrollView.IsEnabled the following way:
<OnIdiom.Tablet>False</OnIdiom.Tablet>
My assumption that using InputTransparent was not correct. Is there a way to make the buttons clickable inside a scroll view that has scrolling disabled?
I essentially am looking for something like Orientation=None, but that is not an option.
You need to write a CustomRenderer for disabling the scroll.
On iOS UIScrollView has a ScrollEnabled property
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
// IsScrollEnabled just a custom property
// handled it in OnPropertyChanged too
ScrollEnabled = Element.IsScrollEnabled;
}
Android it is a bit tricky, there is not direct property. We intercept the touch event and return without handling it.
public override bool OnInterceptTouchEvent(MotionEvent ev)
{
if (Element.IsScrollEnabled)
{
return base.OnInterceptTouchEvent(ev);
}
else
{
return false;
}
}
public override bool OnTouchEvent(MotionEvent ev)
{
if (Element.IsScrollEnabled)
{
return base.OnTouchEvent(ev);
}
else
{
return false;
}
}
I ended up using this approach to disable vertical scrolling on an iPad, which is my target device. Not perfect for android 7 inch tablets, but oh well:
<ScrollView.Orientation>
<OnPlatform x:TypeArguments="ScrollOrientation">
<On Platform="iOS">
<OnIdiom x:TypeArguments="ScrollOrientation">
<OnIdiom.Phone>Both</OnIdiom.Phone>
<OnIdiom.Tablet>Horizontal</OnIdiom.Tablet>
</OnIdiom>
</On>
<On Platform="Android">
<OnIdiom x:TypeArguments="ScrollOrientation">
<OnIdiom.Phone>Both</OnIdiom.Phone>
<OnIdiom.Tablet>Both</OnIdiom.Tablet>
</OnIdiom>
</On>
<On Platform="UWP">Both</On>
</OnPlatform>
</ScrollView.Orientation>
In the latest version of Xamarin Forms, you can set the Orientation to Neither.
scrollV.Orientation = ScrollOrientation.Neither;
Cool and compact way to disable scrolling in Xamarin Forms without affecting it's children through ScrollEnabled extension method:
public static class ScrollViewEx
{
/// <summary>
/// Disables scrollview by modifying Scrolled Event and attaching itself to ScrollView's binding context
/// Scrolled event sends it back to the original x,y
/// </summary>
public class DisabledScrollClass : IDisposable
{
private double ScrollX;
private double ScrollY;
private object OldContext;
private ScrollView Parent;
public DisabledScrollClass(ScrollView parent)
{
Parent = parent;
ScrollX = parent.ScrollX;
ScrollY = parent.ScrollY;
OldContext = parent.BindingContext;
parent.Scrolled += Scrolled;
parent.BindingContext = this;
}
private void Scrolled(object sender, ScrolledEventArgs e)
{
(sender as ScrollView)?.ScrollToAsync(ScrollX, ScrollY, false);
}
public void Dispose()
{
Parent.Scrolled -= Scrolled;
Parent.BindingContext = OldContext;
}
}
public static ScrollView ScrollEnabled(this ScrollView scroll, bool isEnabled)
{
DisabledScrollClass binding = scroll.BindingContext as DisabledScrollClass;
if (isEnabled && binding != null)
binding.Dispose();
if (!isEnabled && binding == null)
_ = new DisabledScrollClass(scroll);
return scroll;
}
}

Is instantiating a class in VM, MVVM compliant ? How else if so?

I have a contentpage and a ContentView with the content property bound to the view model
MainPage:
`
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage x:Class="MvvM.Views.MainPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MvvM.Views"
xmlns:vm="clr-namespace:MvvM.ViewModels">
<!-- ViewModel BindingContext -->
<ContentPage.BindingContext>
<vm:MainViewModel />
</ContentPage.BindingContext>
<Grid>
<Grid.RowDefinitions>
<!-- Header Row -->
<RowDefinition Height="50" />
<!-- ContentView Row -->
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Header -->
<Grid Grid.Row="0"
BackgroundColor="CornflowerBlue"
VerticalOptions="FillAndExpand">
<!-- Button On Header -->
<Button Command=""
Text="Page Switch"
VerticalOptions="Center">
<Button.GestureRecognizers>
<TapGestureRecognizer Command="TapGestureCommand" />
</Button.GestureRecognizers>
</Button>
</Grid>
<!-- Content Container -->
<Grid Grid.Row="1" VerticalOptions="Center">
<ContentView Content="{Binding DisplayPage}" />
</Grid>
</Grid>
</ContentPage>
`
ViewModel:
`using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using MvvM.Views;
using Xamarin.Forms;
namespace MvvM.ViewModels
{
public class MainViewModel :INotifyPropertyChanged
{
public MainViewModel()
{
DisplayPage = new Views.MainPage();
}
private ContentPage _displayPage;
public ContentPage DisplayPage
{
get { return _displayPage; }
set
{
if (value != _displayPage)
{
_displayPage = value;
}
}
}
private ContentView _contentToDisplayView;
public ContentView SelectedView
{
get => _contentToDisplayView;
set
{
_contentToDisplayView = value;
OnPropertyChanged();
}
}
public Command TapGestureCommand
{
get
{
return new Command(TapGesture);
}
}
private void TapGesture()
{
_contentToDisplayView = new RedView();
_displayPage.Content = _contentToDisplayView.Content;
OnPropertyChanged();
}
#region PropertyChangedHandler
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}`
and the second page called "RedPage" want to access the content from
`
<?xml version="1.0" encoding="utf-8" ?>
<ContentView x:Class="MvvM.Views.RedView"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:MvvM.ViewModels"
BindingContext="vm:MainViewModel">
<ContentView.Content>
<Grid Width="*"
Height="*"
BackgroundColor="Red" />
</ContentView.Content>
</ContentView> `
The outcome I want is the ContentView content on the RedPage to be displayed in the mainpage contentview.
is creating an instance of the redpage in the view model MVVM complaint ? (I feel that this would tightly bind view to view model?)
how else can i get the content property on red page into the view model ?(cant bind it and sets elements in it as you can only set content property once)
Ideally you would want the ViewModel not to know anything about the View and vice versa, so from that perspective this is not something you would want.
To overcome this, you would want ViewModel-to-ViewModel navigation. So, you just specify to which ViewModel you want to go and the associated View will be loaded. You can implement this manually, and depending on your chosen implementation you would have some way of resolving a View that is linked to that ViewModel.
One way to do this would be by naming conventions and reflection. This means you name all your pages like:
MyPage
YourPage
OurPage
And all the ViewModels like:
MyPageModel
YourPageModel
OurPageModel
Then with reflection you can simply strip off the "Model" suffix and resolve the page from there. Note that I use the Page and PageModel naming, but of course this works for View and ViewModel as well. After you do, you will still have to account for the navigation to and from this views, is it modal or not, etc.
While you can implement all of this manually it would probably be worth while to look into a MVVM framework. The method I just described is how FreshMvvm does this for instance. But there are other good frameworks out there like Prism, Exrin, MvvmCross, etc.

UWP Prism NavigationService configuration

I've started a UWP project with Prism Library. To navigate between pages I'm using Prism NavigationService. Well everything is ok while I have situation as in bellow image:
Well after login I need to load a window with a split view and later to use NavigationService to change only content of the "red square".
My third page xaml looks like:
<SplitView x:Name="rootSplitView" Style="{StaticResource MenuSplitViewStyle}" Grid.Row="1">
<Frame x:Name="contentFrame"/>
<SplitView.Pane>
<views:MenuViewPage/>
</SplitView.Pane>
</SplitView>
<ToggleButton x:Name="togglePaneButton"
VerticalAlignment="Center"
IsChecked="{Binding IsPaneOpen,
ElementName=rootSplitView,
Mode=TwoWay}"
Style="{StaticResource SplitViewTogglePaneButtonStyle}"
TabIndex="1" />
My app bootstrap is:
public sealed partial class App : PrismUnityApplication
{
protected override Task OnLaunchApplicationAsync(LaunchActivatedEventArgs args)
{
if (args != null && !string.IsNullOrEmpty(args.Arguments))
{
// The app was launched from a Secondary Tile
// Navigate to the item's page
//NavigationService.Navigate("ItemDetail", args.Arguments);
}
else
{
// Navigate to the initial page
NavigationService.Navigate(PageTokens.IntroPage, null);
}
Window.Current.Activate();
return Task.FromResult<object>(null);
}
...
}
My question is how I can create a region in red square and configure navigation service to use the region to navigate between pages.