I have a Border control that contains a couple of Label controls. The text shown in the labels changes, and this causes the Border control to expand and shrink horizontally. To prevent flickering, I want it to be able to expand but not shrink. Is there a way to do this?
Alternatively, is there a way to specify that the Label objects should expand horizontally but never shrink - which would give the same outcome?
My XAML is:
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="uk.andyjohnson.TakeoutExtractor.Gui.ProgressOverlay">
<Border
Stroke="#0f0f0f"
StrokeThickness="2"
StrokeShape="RoundRectangle 5,5,5,5"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"
Padding="30,30">
<VerticalStackLayout
Spacing="25" >
<Label
x:Name="SourceLabel"
Text=""
HorizontalOptions="Center"/>
<Label
x:Name="DestinationLabel"
Text=""
HorizontalOptions="Center" />
</VerticalStackLayout>
</Border>
</ContentView>
Thanks!
You could play with labels WidthRequest.
When your label increase in size, you set its WidthRequest to its current Width value.
Assuming for simplicity that you're changing the label text in your code-behind and that there is only one label, something like this should do the job.
public partial class ProgressOverlay
{
private double currentWidth = 0;
// I create this method just to explain.
// It could be anything in your code that changes your label text
void ChangingText()
{
// ******
// .... SourceLabel text changes here ...
// *******
if (SourceLabel.Width > currentWidth)
{
SourceLabel.WidthRequest = SourceLabel.Width;
currentWidth = SourceLabel.Width;
}
}
}
I solved the problem using a variation on Riccardo Minato's suggestion.
Hook into the SizeChanged event on the bounding Border object.
Track its maximum width using a member variable
When the width exceeds the previous maximum, set the Border object's MinimumWidthRequest property to the new maximum width. This stops it shrinking.
New XAML:
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="uk.andyjohnson.TakeoutExtractor.Gui.ProgressOverlay">
<Border
Stroke="#0f0f0f"
StrokeThickness="2"
StrokeShape="RoundRectangle 5,5,5,5"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"
Padding="30,30"
SizeChanged="Border_SizeChanged"> <!-- ** Added this line ** -->
<VerticalStackLayout
Spacing="25" >
<ActivityIndicator
IsVisible="true"
IsRunning="true"
IsEnabled="true"
HorizontalOptions="Center"/>
<Button
x:Name="CancelButton"
Text="Cancel"
HorizontalOptions="Center"
Clicked="OnCancelButtonClicked"/>
<Label
x:Name="SourceLabel"
Text=""
HorizontalOptions="Center"/>
<Label
x:Name="DestinationLabel"
Text=""
HorizontalOptions="Center"/>
</VerticalStackLayout>
</Border>
</ContentView>
New code-behind:
private double currentMaxWidth = 0D;
private void Border_SizeChanged(object sender, EventArgs e)
{
var bdr = sender as Border;
if (bdr.Width > currentMaxWidth)
{
currentMaxWidth = bdr.Width;
bdr.MinimumWidthRequest = currentMaxWidth;
}
}
Related
FlexLayout adjusts the spacing between the rows of content so the whole of its area is filled:
Can it be set to have no spacing between rows, or a fixed one? So that the rows would be packed to the top of the FlexLayout, and below there would be unused space? I have tried different settings (AlignItems, VerticalOptions, AlignContent) but couldn't achieve the desired result...
P.S.: the elements' size is fixed. These are added programmaticaly.
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="MauiApp3.ActivitiesPage">
<Grid RowDefinitions="50,*,75" ColumnDefinitions="200, *">
<ScrollView Grid.RowSpan="2" x:Name="ActScroll">
<VerticalStackLayout x:Name="Activities" Padding="5" />
</ScrollView>
<Frame Padding="5" Grid.Column="1">
<Entry x:Name="ActText" BackgroundColor="Grey" />
</Frame>
<FlexLayout Grid.Row="1" Grid.Column="1" x:Name="Picker" Direction="Row" Wrap="Wrap" Padding="5" />
<HorizontalStackLayout Grid.ColumnSpan="2" Grid.Row="2" Padding="5" >
<Button x:Name="btnAdd" Text="Add" Clicked="btnAdd_Clicked" Margin="10"/>
<Button x:Name="btnSave" Text="Save" Margin="10" IsEnabled="False" Clicked="btnSave_Clicked"/>
<Button x:Name="btnDelete" Text="Delete" Margin="10" IsEnabled="False" Clicked="btnDelete_Clicked"/>
</HorizontalStackLayout>
</Grid>
</ContentPage>
Code behind (fragment):
public ActivitiesPage()
{
InitializeComponent();
//color picker
var ActColors = new Color[]
{
Colors.DimGray,
Colors.DarkGray,
Colors.Linen,
Colors.AntiqueWhite,
Colors.PeachPuff,
Colors.Tan,
Colors.BurlyWood,
Colors.SandyBrown,
Colors.Peru,
Colors.Chocolate,
Colors.Firebrick,
Colors.DarkRed,
Colors.Brown,
Colors.IndianRed,
Colors.LightCoral,
Colors.LightPink,
Colors.PaleVioletRed,
Colors.HotPink,
Colors.DeepPink,
Colors.MediumVioletRed,
Colors.DarkMagenta,
Colors.DarkOrchid,
Colors.Orchid,
Colors.MediumPurple,
Colors.BlueViolet,
Colors.DarkSlateBlue,
Colors.RoyalBlue,
Colors.DodgerBlue,
Colors.DeepSkyBlue,
Colors.LightSkyBlue,
Colors.PaleTurquoise,
Colors.Turquoise,
Colors.MediumTurquoise,
Colors.Aqua,
Colors.LightSeaGreen,
Colors.SeaGreen,
Colors.DarkGreen,
Colors.ForestGreen,
Colors.LimeGreen,
Colors.PaleGreen,
Colors.YellowGreen,
Colors.Olive,
Colors.DarkGoldenrod,
Colors.DarkOrange,
Colors.Gold,
Colors.Orange,
Colors.OrangeRed,
Colors.Crimson,
Colors.DarkSalmon,
Colors.Coral,
};
foreach (var col in ActColors)
{
btn.Margin = 2;
btn.MinimumWidthRequest = 50;
btn.MaximumWidthRequest = 50;
btn.MaximumHeightRequest = 50;
btn.MinimumHeightRequest= 50;
btn.FontSize = 12;
btn.CornerRadius = 2;
Picker.Add(btn);
}
}
(this is why, source code goes a long way, when you post question)
Your FlexLayout is solved by:
AlignContent="Start" AlignItems="Start" Direction="Row" Wrap="Wrap"
There is nothing else that is needed to have what you want.
The interesting part is the code that is around this FlexLayout. How will you make sure that it gets its desired Width and Height? Easy way is to set its Width and Height request.
(Good way to write interface, is to set faint background of containers, to watch its borders when you make changes.)
Edit: Tested it:
<FlexLayout x:Name="flex" AlignContent="Start" AlignItems="Start" Direction="Row" Wrap="Wrap" BackgroundColor="AliceBlue" HorizontalOptions="FillAndExpand" HeightRequest="500">
And populated it:
for (int i = 0; i < 100; i++)
{
Button b = new Button();
b.WidthRequest = 25;
b.HeightRequest = 25;
b.Text = i.ToString();
flex.Add(b);
}
Everything is exactly like in your picture.
I am trying to create a list view where the background color alternates for each entry in the list. Is there a way to do this in MAUI?
This can be done in multiple ways.And the method often recommended is through a DataTemplateSelector.
1.Create a DataTemplateSelector that holds two templates and selects them based on the index of the item:
public class AlternateColorDataTemplateSelector: DataTemplateSelector
{
public DataTemplate EvenTemplate { get; set; }
public DataTemplate UnevenTemplate { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
// TODO: cast `Monkey` to your Item
return ((List<Monkey>)((ListView)container).ItemsSource).IndexOf(item as Monkey) % 2 == 0 ? EvenTemplate : UnevenTemplate;
}
}
2.In XAML, we can define two templates, one with the alternate color and one with the normal color.
<?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="MauiApp0606.MainPage"
xmlns:local="clr-namespace:MauiApp0606"
>
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="evenTemplate">
<ViewCell>
<Grid BackgroundColor="White">
<Label Text="{Binding Name}" HorizontalOptions="Center" VerticalOptions="Center" />
</Grid>
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="unevenTemplate">
<ViewCell>
<Grid BackgroundColor="LightGray">
<Label Text="{Binding Name}" TextColor="White" HorizontalOptions="Center" VerticalOptions="Center" />
</Grid>
</ViewCell>
</DataTemplate>
<local:AlternateColorDataTemplateSelector x:Key="alternateColorDataTemplateSelector"
EvenTemplate="{StaticResource evenTemplate}"
UnevenTemplate="{StaticResource unevenTemplate}" />
</ResourceDictionary>
</ContentPage.Resources>
<VerticalStackLayout Spacing="25" Padding="30">
<ListView ItemsSource="{Binding Monkeys}" ItemTemplate="{StaticResource alternateColorDataTemplateSelector}">
</ListView>
</VerticalStackLayout>
</ContentPage>
To solve this problem I added a LineNumber property to my view model and used a value converter to set the color.
The value converter:
public class NumberToAlternatingColorValueConverter : IValueConverter
{
public Color EvenNumberColor { get; init; } = Colors.Green;
public Color OddNumberColor { get; init; } = Colors.Red;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is not int intValue
? Colors.Transparent
: (intValue % 2) == 0
? EvenNumberColor
: OddNumberColor;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException("NumberToAlternatingColorValueConverter.ConvertBack");
}
}
Register the value converter
I registered the value converter in my main resource dictionary like so:
<converter:NumberToAlternatingColourValueConverter
x:Key="MagentaCyanRowColorConverter"
EvenNumberColour="Magenta"
OddNumberColour="Cyan" />
You can register more than one value converter with a different key for different colour combinations.
Use the value converter
And I used the bound value converter in my XAML like so:
<StackLayout BindableLayout.ItemsSource="{Binding Summary.Lines}">
<BindableLayout.ItemTemplate>
<DataTemplate x:DataType="vm:SummaryLineViewModel">
<Grid BackgroundColor="{Binding LineNumber, Converter={StaticResource MagentaCyanRowColorConverter}}">
<Label Grid.Column="0" Text="{Binding Name}" />
<Label Grid.Column="1" Text="{Binding Value}" />
</Grid>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
As in this answer we can used DataTemplateSelector to resolve your problem.
Alternative solutions,
You can use a property in the model and straight a way bind it to the row background. (In your case Grid's Background color)
You can use triggers make the color change by considering a logic.
( Xamarin Forms Triggers )
I have used a litle XamarinForms before and then i did a
naming to be able to point the c# code to it.
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiTest.MainPage"
BackgroundColor="{DynamicResource SecondaryColor}">
<StackLayout x:name="_stacklayoutname">
<Label Text="" x:Name="_Lable"/>
</StackLayout>
</ContentPage>
And then i did like this in the MainPage.xaml.cs
public MainPage()
{
InitializeComponent();
_stacklayoutname.Children.Add(new Label { Text = "TEST" });
_Lable.Text = "TEST";
}
now it get this, but i can change the _Lable to "Text".
how can i point to a stacklayout.
Severity Code Description Project File Line Suppression State
Error CS0103 The name '_stacklayoutname' does not exist in the current context MauiTest (net6.0-android), MauiTest (net6.0-ios), MauiTest (net6.0-maccatalyst), MauiTest (net6.0-windows10.0.19041) *** 11 Active
this is wrong
<StackLayout x:name="_stacklayoutname">
it should be
<StackLayout x:Name="_stacklayoutname">
I am trying to hide/show a row in a grid (filled dynamically from Java code) upon clicking on it's previous row. To simulate a simple MasterDetail component. I get the right index of the clicked row in the java code, however changing visibility of the row doesn't work! Can anyone help me with this or is there a similar way to to this?
Thanks,
Pooya
Here is the code for the ZUL:
<?xml version="1.0" encoding="UTF-8"?>
<zk xmlns="http://www.zkoss.org/2005/zul">
<window id="callbackLogWindow"
apply="CallbackLogWindowComposer"
border="none" height="100%" width="100%"
xmlns:w="http://www.zkoss.org/2005/zk/client">
<grid id="callbackLogGrid" oddRowSclass="non-odd" height="100%">
<columns>
<column label="Logging Name" />
<column label="Status" />
<column label="DateTime" />
<column label="Subject" />
<column label="Replies" />
</columns>
<rows>
<zk forEach="${callbackLogWindow$composer.callbacks}">
<row sclass='${forEachStatus.index % 2 != 0 ? "z-grid-odd" : ""}'
onClick="callbackLogWindow$composer.toggleRow(self.index)">
<custom-attributes callback="${each}"/>
<cell><label value="${callback.loggingName}" /></cell>
<cell><label value="${callback.resolved}" /></cell>
<cell><label value="${callback.callbackTime}" /></cell>
<cell><label value="${callback.subject}" /></cell>
<cell><label value="${callback.resolvedItemCount}" /></cell>
</row>
<row sclass='${forEachStatus.index % 2 != 0 ? "z-grid-odd" : ""}'>
<cell colspan="5">
<include src="callbackItem.zul" callback="${each}"/>
</cell>
</row>
<row>
<custom-attributes callback="${each}"/>
<button onClick="callbackLogWindow$composer.saveCallbackItems(callback)">
Save
</button>
</row>
</zk>
</rows>
</grid>
</window>
</zk>
And the controller:
public class CallbackLogWindowComposer extends SelectorComposer<Window> {
#Inject private CallbackDao callbackDao;
#Wire Grid callbackLogGrid;
private List<Callback> callbacks = new ArrayList<Callback>();
#Override
public void doAfterCompose(Window window) throws Exception {
super.doAfterCompose(window);
}
public List<Callback> getCallbacks() {
callbacks = callbackDao.findAll();
return callbacks;
}
public void toggleRow(int i) {
Component row = callbackLogGrid.getRows().getChildren().get(i+1);
row.setVisible(row.isVisible());
callbackLogGrid.renderAll();
}
public void saveCallbackItems(Callback cb) {
callbackDao.saveInTransaction(cb);
}
}
I am not sure and not test but i can suggest one thing here to you Zk have visible="true/false" attribute you can apply this attribute in each row and bind this with your databean variable For more detail i can suggest let us suppose you have a List<A> list
and list contain all the record which you want to display now add another variable like display in class A and control it by your self .And onClick on any row change update any other item of list .
I have the same problem, and the solution I found was:
row visible=""
<grid model="#bind(vm.total)" vflex="1" emptyMessage="No records exist" width="315px" >
<columns>
<column width="45%"/>
<column width="25%" />
</columns>
<template name="model">
<row visible="#load(each.visible)">
<checkbox label="#load(each.label)" style="font-weight:bold" if="${each.checkbox}"/>
<label value="#load(each.label)" style="font-weight:bold" unless="${each.checkbox}"/>
<doublebox value="#bind(each.value)" sclass="textBoxNumber" locale="us" readonly="true" />
</row>
</template>
</grid>
</groupbox>
In my scenario I have a MainView + MainViewModel, UserControl1 + UserControl 2.
In the MainView I have 2 buttons labeled: Button_ShowUserControl1 + Button_ShowUserControl2.
At the lower part of the MainView I have a "ContentGrid" which takes/should_take... every UserControl.
My goal:
When Button_ShowUserControl1 is clicked UserControl1 is Visible and UserControl2 OR any other UserControl must be set to Collapsed. Same is valid for Button_ShowUserControl2.
My problem:
1.) As the UserControls shall be loaded at application start how can I put them all together in the one "ContentGrid"? Thats actually not possible... so how can I make one UserControl visible while the other is in the same place/"ContentGrid" just collapsed ?
2.) As 1.) seems not possible how can I instantiate all UserControls at start of the application and make them only Visible/Collapsed when respective Button is clicked?
3.) As a UserControl has a property Visibility = Visible/Hidden/Collapsed, how can I bind to a property in a ViewModel return such a value like Collapsed? I only could get a boolean value like Visibility = false/true ?
My testcode:
<Grid x:Name="LayoutRoot" Background="#FFBDF5BD" ShowGridLines="False">
<Grid.RowDefinitions>
<RowDefinition Height="96*" />
<RowDefinition Height="289*" />
</Grid.RowDefinitions>
<Grid HorizontalAlignment="Stretch" Name="MenuGrid" VerticalAlignment="Stretch" Background="#FFCECEFF">
<StackPanel Name="stackPanel1" Background="#FFEDFF00" Orientation="Horizontal">
<Button Content="User Data 1" Height="35" Name="button1" Command="{Binding Path=ShowUserControl1Command}" Width="150" Margin="100,0,0,0" />
<Button Content="User Data 2" Height="35" Name="button2" Width="150" Margin="100,0,0,0" />
</StackPanel>
</Grid>
<Grid Grid.Row="1" HorizontalAlignment="Stretch" Name="ContentGrid" VerticalAlignment="Stretch" Background="#FFB15454" />
</Grid>
<UserControl x:Class="SwapUserControls.MVVM.UserControl2"
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"
xmlns:vm="clr-namespace:SwapUserControls.MVVM.ViewModel"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" Visibility="{Binding IsUserControl1Collapsed, Path=Value}">
<UserControl.Resources>
<vm:MainViewModel x:Key="MainViewModelID" />
</UserControl.Resources>
<UserControl.DataContext>
<Binding Source="{StaticResource MainViewModelID}" />
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="228*" />
<RowDefinition Height="72*" />
</Grid.RowDefinitions>
<Button Content="UserControl2" Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="112,27,0,0" Name="button1" VerticalAlignment="Top" Width="75" />
<DataGrid HorizontalAlignment="Stretch" Name="dataGrid1" VerticalAlignment="Stretch" Background="#FFC046F8" />
</Grid>
public class MainViewModel : ViewModelBase
{
RelayCommand _ShowUserControl1Command;
private bool _IsUserControl1Collapsed;
public RelayCommand ShowUserControl1Command
{
get
{
if (_ShowUserControl1Command == null)
{
_ShowUserControl1Command = new RelayCommand( () => ShowUserControl1() );
}
return _ShowUserControl1Command;
}
}
public void ShowUserControl1()
{
_IsUserControl1Collapsed = true;
}
public bool IsUserControl1Collapsed
{
get
{
return _IsUserControl1Collapsed;
}
}
}
Yes the code is wrong, therefore I ask here :)
You only have 2 things wrong with this code.
1) You can't set the visibility of a usercontrol directly... you have to set it on a container:
<Grid Visibility="Collapsed">
<myControls:MyUserControl />
</Grid>
2) Visibility is not a boolean value, it is an enum. As such, you will need to use a converter to convert from boolean to Visibility. Observe:
<Window ...>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis" />
</Window.Resources>
<Grid Visibility="{Binding ShouldShowUsercontrol1, Converter={StaticResource BoolToVis}}">
<myControls:MyUserControl />
</Grid>
</Window>
That should be it. Hope this helps.
There are other things that you are leaving clues about that might affect the ability of this to work. For example, you don't show the biggest container element... are you wrapping everything in a StackPanel? If you are wrapping everything in a Grid, for example, the controls will overlay everything in layers.
Try these changes I suggest... it should get you closer.
Edit: Another idea using data templates
Another thing you could do is make sure you have a unique ViewModel for each of these views you want to show and hide:
public class MyFirstViewModel : ViewModel
{
}
public class MySecondViewModel : ViewModel
{
}
Then from your "parent" or "main" ViewModel, you show or hide the views you want by virtue of having them in a collection of ViewModels:
public MyMainViewModel : ViewModel
{
public ObservableCollection<ViewModel> ViewsToShow
{
...
}
public void ShowFirstViewModel()
{
ViewsToShow.Add(new MyFirstViewModel());
}
}
To wire everything up in your view, you would then datatemplate these types with their user controls (but this would not cause those views to be instantiated unless they were needed:
<Window ...>
<Window.Resources>
<DataTemplate DataType="{x:Type myViewModels:MyFirstViewModel}">
<myViews:MyFirstView />
</DataTemplate>
<DataTemplate DataType="{x:Type myViewModels:MySecondViewModel}">
<myViews:MySecondView />
</DataTemplate>
</Window.Resources>
<ItemsControl ItemsSource="{Binding ViewsToShow}" />
</Window>
And for any ViewModels you put in "ViewsToShow", the view will automatically see that and template in the appropriate view. Again, without instantiating it before it's needed.
This is probably a little cleaner than putting everything single thing in the View and setting visibility, but it would be dependent on you have a unique view model type for every view, which might not be the case.
The question of saving state comes up when using the DataTemplated approach. The solution here is to tread your ViewModel as the state of the control and design both your ViewModels and your Views accordingly. Here is an example that allows you to swap out your Views using DataTemplating, but switching back and forth saves state.
Assume you have the setup from the last section w/ 2 viewmodels that have datatemplates defined. Let's change up the MainViewModel a little:
public MyMainViewModel : ViewModel
{
public RelayCommand SwapViewsCommand
{
...
}
public ViewModel View
{
...
}
private ViewModel _hiddenView;
public MyMainViewModel()
{
View = new MyFirstViewModel();
_hiddenView = new MySecondViewModel();
SwapViewsCommand = new RelayCommand(SwapViewModels);
}
public void SwapViewModels()
{
var hidden = _hiddenView;
_hiddenView = View;
View = hidden;
}
}
And a few changes to the main view. I've omitted the DataTemplates for brevity.
<Window ...>
<!-- DataTemplates Here -->
<Button Command="{Binding SwapViewsCommand}">Swap!</Button>
<ContentControl Content="{Binding View}" />
</Window>
That's it. The secret here is I'm saving the reference to the original view model. This way, let's say there is a string property in a viewmodel and an associated textbox in the DataTemplated usercontrol with a two-way binding then the state will essentially be saved.