Fixed space between rows in FlexLayout? - maui

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.

Related

Prevent a Border control from shrinking when its child controls shrink

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;
}
}

Add Control programmatically, UI is not updating

I am just playing arround with .Net MAUI.
I want to retrieve information from a Rest service and based on the result I want to add Buttons programmatically to a VerticalStackLayout.
When I debug the solution the buttons are added to the VerticalStackLayout but the UI is not updated.
Here the code Snippet
var btn = new Button();
btn.Text = "Button " + count + " added";
btn.Clicked += OnCounterClicked;
btn.HorizontalOptions = LayoutOptions.Center;
VLayout.Add(btn);
Here the XAML
<ScrollView>
<VerticalStackLayout x:Name="VLayout"
Spacing="25"
Padding="30,0"
VerticalOptions="Center">
<Image
Source="dotnet_bot.png"
SemanticProperties.Description="Cute dot net bot waving hi to you!"
HeightRequest="200"
HorizontalOptions="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" />
<Entry x:Name="entry"
Placeholder="Enter text"
TextChanged="OnTextChanged"
Completed="OnTextCompleted" />
<Button
x:Name="KommenBtn"
Text="Kommen"
SemanticProperties.Hint="Counts the number of times you click"
Clicked="OnCounterClicked"
HorizontalOptions="Center" />
<Button
x:Name="GehenBtn"
Text="Gehen"
SemanticProperties.Hint="Counts the number of times you click"
Clicked="OnCounterClicked"
HorizontalOptions="Center" />
</VerticalStackLayout>
</ScrollView>
Thanks in advance for your hints & help!
BW
Try to update UI on main thread , you can wrap your code into Application.Current.Dispatcher.Dispatch .
Sample code
Application.Current.Dispatcher.Dispatch(() =>
{
var btn = new Button();
btn.Text = "Button " + count + " added";
btn.Clicked += OnCounterClicked;
btn.HorizontalOptions = LayoutOptions.Center;
VLayout.Add(btn);
});
To update UI after a change that affects the hierarchy (or positions) of controls:
(VLayout as IView).InvalidateArrange();
NOTE: This is roughly equivalent to Xamarin.Forms layout.ForceLayout();
If not already in code running on UI MainThread, wrap it in Dispatch:
Dispatcher.Dispatch(() =>
(VLayout as IView).InvalidateArrange());

Focus to a specific editable datagrid cell in wpf DataGrid on RowSelection

I have four columns in a datagrid and fourth column is templated and has textbox which is always editable.
What i am trying to achieve here is when a row selection changes the highlighted row's fourth cell which is editable and has textbox in it should get focus.
I am ok to get this done in codebehind or xaml.
Here is what i did:
<DataGrid Name="MyGrid" SelectionChanged="MyGrid_OnSelectionChanged" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Str1}" IsReadOnly="True"/>
<DataGridTextColumn Binding="{Binding Str2}" IsReadOnly="True"/>
<DataGridTextColumn Binding="{Binding Str3}" IsReadOnly="True"/>
<DataGridTemplateColumn >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Str4}" GotFocus="UIElement_OnGotFocus" >
<TextBox.Style >
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=DataGridCell,AncestorLevel=1},Path=IsSelected}">
<Setter Property="FocusManager.FocusedElement" Value="{Binding RelativeSource={RelativeSource Self}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
private void PopulateDummies()
{
int i = 0;
List<SomeDummy> dummies = new List<SomeDummy>();
while (i < 10)
{
dummies.Add(new SomeDummy
{
Str1 = string.Format("Str1:{0}", i),
Str2 = string.Format("Str2:{0}", i),
Str3 = string.Format("Str3:{0}", i),
Str4 = string.Format("Str4:{0}", i)
});
i++;
}
MyGrid.ItemsSource = dummies;
}
private void MyGrid_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
private void UIElement_OnGotFocus(object sender, RoutedEventArgs e)
{
var txtB = sender as TextBox;
txtB.Focus();
txtB.SelectAll();
}
Nothing seems to work. Not sure what is the reason. Can anyone help
The statement
txtB.Focus();
effectively replaces the DataGridCell by a TextEditory control. In order to do this, the view needs to be updated and therefore the UI-tread needs to run.
The SelectAll can not run until the editor is created, so run it as a separate Dispatcher-job and replace it with the following:
Application.Current.Dispatcher.BeginInvoke(new Action(() => {
txtB.SelectAll();
}), DispatcherPriority.Render);
This will do the trick and you don't need the SelctionChanged-event handler for this purpose.

Hide row in dynamic grid in ZK framework

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>

Collapse/Visible UserControls on ButtonClick with MVVM - no swap mechanism -

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.