Uno Main Layout - uno-platform

I am new to Uno and I have been following the frame navigation tutorial. I noticed that when the frame navigates the entire window changes. This is good but not optimal. Is there a way in Uno to have a Main Layout, like you would see in a ASP.Net MVC project? I would rather not implement the navigation menu on each page.

To extend on #matfillion's answer, if NavigationView doesn't suit your needs, you can easily roll your own navigation shell whilst leveraging the built-in frame navigation. There's no requirement for Frame to be the top-level control in your application.
Here's an ultra-simple example, to illustrate the principle. The navigation list will stay visible at the top whilst navigating between pages.
Shell.xaml:
<UserControl x:Class="UnoTestbed44.Shell"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ListView x:Name="NavigationList"
Background="LightGray"
ItemsSource="{x:Bind Pages}"
Grid.Row="0"
DisplayMemberPath="Label"
SelectionChanged="NavigationList_SelectionChanged">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
<Frame x:Name="MainFrame"
Grid.Row="1" />
</Grid>
</UserControl>
Shell.xaml.cs:
using System;
using System.Linq;
using Windows.UI.Xaml.Controls;
namespace UnoTestbed44
{
public sealed partial class Shell : UserControl
{
public NavigationItem[] Pages { get; } = new[] {
new NavigationItem {Label = "First page", PageType = typeof(Page1)},
new NavigationItem {Label = "Second page", PageType = typeof(Page2)},
};
public Shell()
{
this.InitializeComponent();
}
private void NavigationList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.FirstOrDefault() is NavigationItem navigationItem)
{
MainFrame.Navigate(navigationItem.PageType);
}
}
public class NavigationItem
{
public string Label { get; set; }
public Type PageType { get; set; }
}
}
}
Override OnLaunched() in App.xaml.cs:
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
#if NET5_0 && WINDOWS
_window = new Window();
_window.Activate();
#else
_window = Windows.UI.Xaml.Window.Current;
#endif
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (_window.Content == null)
{
_window.Content = new Shell();
}
#if !(NET5_0 && WINDOWS)
if (e.PrelaunchActivated == false)
#endif
{
// Ensure the current window is active
_window.Activate();
}
}

While not exactly what you are looking for, I believe the NavigationView control is your closest bet. You can disable CompactView and obtain something like what is showcased in UnoGallery.

Related

An emptyView for loading data and another for when there is no data available

I have a case of using a CarouselView that is displayed based on certain data brought from an API, the point is that I need to see a certain view or at least text while the API data is being downloaded and another one in case That there is no data.
I tried to get to this using RefreshView and EmptyView but I cannot achieve the required behavior, I can make an EmptyView appear immediately the data begins to load since at that moment the ItemSource is null, then when the data reaches the app the Carousel appears , which seems to me quite ugly, the ideal would be to show some view that next to the RefreshView indicator shows that the data is loading and then in case of not bringing any data show a view that of the feedback that API data did not return .
I hope I have made myself understood and I hope someone can give me an idea on how to achieve this behavior.
MyViewModel:
public MyViewModel()
{
IsRefreshing = true;
Things = new ObservableCollection<Things>();
var t = Task.Run(async () =>
{
await LoadThings();
});
Task.WhenAll(t);
IsRefreshing = false;
}
private async Task LoadThings()
{
Things = new List<Thing>(await App.WebApiManager.GetThingsAsync(Id));
}
My IsRefreshing property is linked to the IsRefreshing property in the RefreshView that encompasses my CarouselView
I think you could use two empty view and switch between them when the refreshing status changes, and here is the code:
add two content view in in XAML and set default empty view to LoadingData:
<ContentPage.Resources>
<ContentView x:Key="LoadingData">
<StackLayout>
<Label Text="Loading data..."
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</StackLayout>
</ContentView>
<ContentView x:Key="NoDataLoaded">
<StackLayout>
<Label Text="No items to display."
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</StackLayout>
</ContentView>
</ContentPage.Resources>
<StackLayout Margin="20">
<RefreshView IsRefreshing="{Binding IsRefreshing}"
Command="{Binding RefreshCommand}">
<CarouselView x:Name="carouselView"
EmptyView="{StaticResource LoadingData}">
... ...
and in code, show different empty view accordingly:
public partial class HorizontalPullToRefreshPage : ContentPage
{
AnimalsViewModel viewModel;
public HorizontalPullToRefreshPage()
{
InitializeComponent();
viewModel = new AnimalsViewModel();
this.BindingContext = viewModel;
viewModel.PropertyChanged += ViewModel_PropertyChanged;
}
private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName.Equals("IsRefreshing"))
{
if (viewModel.IsRefreshing && viewModel.Animals.Count==0)
{
carouselView.EmptyView = Resources["LoadingData"];
}
else if (!viewModel.IsRefreshing && viewModel.Animals.Count == 0)
{
carouselView.EmptyView = Resources["NoDataLoaded"];
}
}
}
protected override async void OnAppearing()
{
base.OnAppearing();
await Task.Delay(2000);
carouselView.ItemsSource = viewModel.Animals;
}
}
then, every time the property IsRefreshing changed, you got a chance to switch the empty view.
Hope it helps.

How to drag and drop Items between two ListView Controls?

There are two listviews, one of them is grouped and other is not. I have to drag items from one list and drop into other. The drag and drop should be visual like item should actually be seen dragging and dropping on the UI. After some search, I found this tutorial TouchTrackingEffect Demos and thought to apply the same logic to Listview controls. I have somehow managed to apply the touch effects to the ListViews and it is firing events such as pressed, moved and released. I also managed to add items to the grouped listview. However, it is not showing dragging and dropping on the screen. I am assuming, I am doing something wrong but I am new to Xamarin so trying my best to get it working. The attached images show the UI, 2 shows the startup screen, 3 shows the X and Y coordinates in "Moved" effect, and 4 shows "New Word" is added in Grouped ListView on Released event. Below is the code, I would appreciate if someone can help me in this regard.
Page2Grid.Xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TouchTrackingEffect.Models;
using System.Collections.ObjectModel;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using TouchTracking;
using SkiaSharp;
using SkiaSharp.Views.Forms;
namespace TouchTrackingEffect
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Page2Grid : ContentPage
{
//public ObservableCollection<GroupedStructureModel> grouped { get; set; }
ObservableCollection<GroupedStructureModel> grouped = new ObservableCollection<GroupedStructureModel>();
public ObservableCollection<GroupedStructureModel> grouped1 { get { return grouped; } }
// Drag and Drop Class
class DragInfo
{
public DragInfo(long id, Point pressPoint, AbsoluteLayout absoluteLayout)
{
Id = id;
PressPoint = pressPoint;
absoluteLayoutID = absoluteLayout;
}
public long Id { private set; get; }
public AbsoluteLayout absoluteLayoutID { set; get; }
public Point PressPoint { private set; get; }
}
// dictionary for ListViews
Dictionary<ListView, DragInfo> lstStructDragDictionary = new Dictionary<ListView, DragInfo>();
Dictionary<ListView, DragInfo> lstWordsDragDictionary = new Dictionary<ListView, DragInfo>();
Random random = new Random();
public Page2Grid()
{
InitializeComponent();
populateRhymeListView();
// adding effects to ListViews
// Main List
TouchEffect touchEffect1 = new TouchEffect();
touchEffect1.TouchAction += OnTouchEffectAction;
lstViewMain.Effects.Add(touchEffect1);
// Word List
TouchEffect touchEffect2 = new TouchEffect();
touchEffect2.TouchAction += OnTouchEffectAction;
lstViewWords.Effects.Add(touchEffect2);
}
void OnTouchEffectAction(object sender, TouchActionEventArgs args)
{
lstViewWords = sender as ListView;
switch (args.Type)
{
case TouchActionType.Pressed:
// don't allow a second touch on an already touched boxview
if (!lstWordsDragDictionary.ContainsKey(lstViewWords))
{
lstWordsDragDictionary.Add(lstViewWords, new DragInfo(args.Id, args.Location, absLayout));
// set capture property to true
TouchEffect toucheffect = (TouchEffect)lstViewWords.Effects.FirstOrDefault(e => e is TouchEffect);
toucheffect.Capture = true;
}
break;
case TouchActionType.Moved:
if (lstWordsDragDictionary.ContainsKey(lstViewWords) && lstWordsDragDictionary[lstViewWords].Id == args.Id)
{
//lstViewMain.TranslateTo
headerLbl.Text = " listWords is moved";
Rectangle rect = AbsoluteLayout.GetLayoutBounds(lstViewWords);
Point initialLocation = lstWordsDragDictionary[lstViewWords].PressPoint;
rect.X += args.Location.X - initialLocation.X;
rect.Y += args.Location.Y - initialLocation.Y;
AbsoluteLayout.SetLayoutBounds(lstViewWords, rect);
headerLbl.Text = "X = " + args.Location.X + " Y = " + args.Location.Y;
}
break;
case TouchActionType.Released:
if (lstWordsDragDictionary.ContainsKey(lstViewWords) && lstWordsDragDictionary[lstViewWords].Id == args.Id)
{
grouped1.FirstOrDefault().Add(new StructureModel { word = "New Word" });
}
break;
}
}
// Create a Grouped ListView from a Rhyme Description
void populateRhymeListView()
{
// defining two lines
GroupedStructureModel list1Group = new GroupedStructureModel() { LongName = "Line1" };
GroupedStructureModel list2Group = new GroupedStructureModel() { LongName = "Line2" };
// words for List 1
//List<WordList> wordLst1 = new List<WordList>();
list1Group.Add(new StructureModel { word = "first"});
// words for List
list2Group.Add(new StructureModel { word = "second1" });
grouped1.Add(list1Group);
grouped1.Add(list2Group);
// binding list itemsource
lstViewMain.ItemsSource = grouped1;
}
async void BtnNext_Clicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new Page3());
}
}
}
****Page2Grid.Xaml****
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage 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="TouchTrackingEffect.Page2Grid">
<ContentPage.Content>
<AbsoluteLayout x:Name="absLayout">
<StackLayout Orientation="Vertical" VerticalOptions="FillAndExpand">
<Grid x:Name="gridLayout">
<Grid.RowDefinitions>
<RowDefinition Height="50"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"></ColumnDefinition>
<ColumnDefinition Width="300"></ColumnDefinition>
<ColumnDefinition Width="300"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label x:Name="headerLbl" Text="Welcome to Xamarin.Forms!"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" />
<Button x:Name="btnNext" Grid.Row="0" Grid.Column="2" Text="Next" Clicked="BtnNext_Clicked"></Button>
<!--Main ListView containing structure -->
<ListView x:Name ="lstViewMain" Grid.Row="1" Grid.Column="0" IsGroupingEnabled="true" GroupDisplayBinding="{Binding LongName}" WidthRequest="200" BackgroundColor="Azure">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label x:Name="lblItem" Text="{Binding word}"></Label>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!--2nd Column-->
<BoxView x:Name="test" Grid.Row="1" Grid.Column="1"></BoxView>
<!-- Word List Views-->
<ListView x:Name="lstViewWords" Grid.Row="1" Grid.Column="2" BackgroundColor="AliceBlue">
<ListView.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Baboon</x:String>
<x:String>Capuchin Monkey</x:String>
<x:String>Blue Monkey</x:String>
<x:String>Squirrel Monkey</x:String>
<x:String>Golden Lion Tamarin</x:String>
<x:String>Howler Monkey</x:String>
<x:String>Japanese Macaque</x:String>
</x:Array>
</ListView.ItemsSource>
</ListView>
</Grid>
</StackLayout>
</AbsoluteLayout>
</ContentPage.Content>
</ContentPage>

Xamarin forms: Entire row highlighting when tapped FlowListView item?

I have added FlowListView in my project. As mention in the FAQ I am facing the entire row highlighting issue when tapped one item in windows, no such issue in android. I have added the custom renderers like below:
In Main Project:
using DLToolkit.Forms.Controls;
namespace Mynamespace
{
public class CustomFlowListView : FlowListView
{
}
}
In UWP:
using Listpm;
using Listpm.UWP;
using Xamarin.Forms;
using Xamarin.Forms.Platform.UWP;
[assembly: ExportRenderer(typeof(CustomFlowListView), typeof(CustomListViewRenderer))]
namespace Listpm.UWP
{
class CustomListViewRenderer : ListViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
{
base.OnElementChanged(e);
if (List != null)
List.SelectionMode = Windows.UI.Xaml.Controls.ListViewSelectionMode.None;
}
}
}
In xaml added <local:CustomFlowListView> instead of <flv:FlowListView>.
<local:CustomFlowListView
FlowColumnCount="2"
SeparatorVisibility="None"
HasUnevenRows="false"
RowHeight="200"
FlowItemsSource="{Binding AllItems}">
<flv:FlowListView.FlowColumnTemplate>
<DataTemplate>
<StackLayout
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand">
<Image/>
</StackLayout>
</DataTemplate>
</flv:FlowListView.FlowColumnTemplate>
</local:CustomFlowListView>
Are there any other changes instead of this for solving this issue?
How can I disable entire row highlighting when tapped?
You also need to add List.IsItemClickEnabled = false. And it will not effect FlowItemTapped event.
protected override void
OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.ListView> e)
{
base.OnElementChanged(e);
if (List != null)
List.SelectionMode = Windows.UI.Xaml.Controls.ListViewSelectionMode.None;
List.IsItemClickEnabled = false;
}

Custom Render not being used on Bindable property update

Please consider the following issue.
In my Xamarin.Forms app I have a custom render for UWP that allows for a button to have two lines, and be centralised.
The buttons in questions are items in a Listview that are bound to objects. When they are initially generated, they display correctly with both lines of text in the center of the button, however if I update the text, it updates, but seems to bypass the custom renders "be in the center" code.
Please see the below code snippets and images to explain the situation further.
Custom Render
[assembly: ExportRenderer(typeof(TwoLinedButton), typeof(TwoLinedButtonUWP))]
namespace aphiresawesomeproject.UWP
{
public class TwoLinedButtonUWP : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (Control != null && e.NewElement.Text != null)
{
var textBlock = new Windows.UI.Xaml.Controls.TextBlock
{
Text = e.NewElement.Text,
TextAlignment = Windows.UI.Xaml.TextAlignment.Center,
TextWrapping = TextWrapping.WrapWholeWords
};
Control.Content = textBlock;
}
}
}
}
XAML
<ListView x:Name="AphiresListView" CachingStrategy="RecycleElement" ItemsSource="{Binding ListViewItems}" Margin="0,20,0,0" RowHeight="130" SeparatorVisibility="None" VerticalOptions="FillAndExpand" Grid.Column="1" Grid.Row ="3" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<local:TwoLinedButton Command="{Binding ClickedCommand}" Margin="5,10,5,10" HorizontalOptions ="FillAndExpand" BackgroundColor="{Binding color_hex}" Grid.Column="1" TextColor="{StaticResource LightTextColor}" FontSize="Medium" Text="{Binding problem_title}"></local:TwoLinedButton>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Update in Viewmodel
foreach (AphiresObject ViewItem in ListViewItems)
{
ViewItem.problem_title = ViewItem.problem_title.Replace("Line 2", "Updated Line 2");
}
Before
After
I think all you need to do is override OnElementPropertyChanged in your renderer and set the textBlock properties again when your text property changes.
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == TwoLinedButton.TextProperty.PropertyName)
{
//Set text block properties
}
}
You may also need to tell the view to re-render itself.
iOS: this.SetNeedsDisplay();
Android: this.Invalidate();

How to I handle keyboard appearance/disappearance in xamarin.forms

I have a topmost grid
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="120"/>
<RowDefinition Height="1"/>
<RowDefinition Height="5"/>
<RowDefinition Height="35"/>
<RowDefinition Height="5"/>
</Grid.RowDefinitions>
in my page.
How could I have * row to adjust it's height to respect the presence or absence of on screen keyboard?
So that the content in the row 0 shrinks as the keyboard appears.
Or, at the very least how could I detect keyboard shows up
on an Editor?
I have a custom renderer for that Editor already
so stuffing extra platform specific code can be done swiftly.
You can control Grid row size only manually. Editor.Focused and Editor.Unfocused is what you're looking for.
But you can combine it with event Triggers (http://developer.xamarin.com/guides/cross-platform/xamarin-forms/working-with/triggers/):
<EventTrigger Event="Focused">
<local:FocusedTriggerAction />
</EventTrigger>
public class FocusedTriggerAction : TriggerAction<Editor>
{
protected override void Invoke (Editor editor)
{
yourRow.Height = new GridLength(100);
}
}
For now I've adapted the solution found here
http://www.gooorack.com/2013/08/28/xamarin-moving-the-view-on-keyboard-show/
private UIView activeview; // Controller that activated the keyboard
/// <summary>
/// Initializes a new instance of the <see cref="ExtendedEditorRenderer"/> class.
/// </summary>
public ExtendedEditorRenderer ()
{
NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.DidShowNotification,KeyBoardUpNotification);
NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillHideNotification,KeyBoardDownNotification);
}
public void Dispose()
{
NSNotificationCenter.DefaultCenter.RemoveObserver(UIKeyboard.DidShowNotification);
NSNotificationCenter.DefaultCenter.RemoveObserver(UIKeyboard.WillHideNotification);
base.Dispose();
}
private void KeyBoardDownNotification(NSNotification notification)
{
try
{
var view = (ExtendedEditor)Element;
if (view.KeyboardListener != null)
{
Size s = new Size(0, 0);
view.KeyboardListener.keyboardSizeChangedTo(s);
}
}
catch (Exception ex)
{
//Debug.WriteLine("dcaught {0}", ex);
}
}
private void KeyBoardUpNotification(NSNotification notification)
{
try
{
// get the keyboard size
CGRect r = UIKeyboard.BoundsFromNotification(notification);
Size s = new Size(r.Size.Width, r.Size.Height);
var v = (ExtendedEditor)Element;
v.KeyboardListener.keyboardSizeChangedTo(s);
}
catch(Exception ex) {
//Debug.WriteLine("scaught {0}", ex);
}
}
shared platform "independent" code:
public interface IKeyboardListener
{
void keyboardSizeChangedTo(Size s);
}
public class ExtendedEditor : Editor ...
{

Resources