I find the grid control to be very messy, counter-intuitive, verboose, and breaking the idea of xml that position in the document is important to layout. I spent a lot of time programming in the Adobe Flex framework and found I was incredibly fast at UI development with that ability, and the UI is way easier to parse later on as well to update and maintain. With that in mind how do we bring the ability to make controls like stackpanel, and button that can tolerate percentage widths and heights?
Documenting this here so it might help someone. I came from Adobe Flex, and using percentage based widths and heights is a breeze and I find the grid control to be messy and ruins half of the point of using XML to define a UI by breaking the layout order and style and adds a lot of code for little value. Here is an example:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:s="clr-namespace:Sandbox.Spark"
x:Class="Sandbox.MainWindow" Padding="5">
<s:VGroup>
<Border Background="LightBlue" CornerRadius="5" PercentHeight="30" PercentWidth="50">
<Button Content="Test" HorizontalAlignment="Center"/>
</Border>
<Border Background="Green" CornerRadius="5" Height="200" PercentWidth="75" Padding="5">
<s:VGroup>
<Button Content="Test5" PercentWidth="50"/>
<Button Content="Test8"/>
</s:VGroup>
</Border>
<Border Background="LightGray" CornerRadius="5" PercentHeight="100" PercentWidth="100">
<s:HGroup>
<Button Content="Test2"/>
<Button Content="Test3"/>
</s:HGroup>
</Border>
</s:VGroup>
</Window>
I Created the classes Group, VGroup, and HGroup, which are similar to StackPanel's but better suited to dealing with percentage based layout. Here they are:
/// <summary>
/// A Panel control similar to StackPanel but with greater support for PercentWidth and PercentHeight
/// </summary>
public class Group : Panel
{
public static readonly StyledProperty<Orientation> OrientationProperty = AvaloniaProperty.Register<Group, Orientation>(
"Orientation", Orientation.Vertical);
public Orientation Orientation
{
get => GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
public static readonly StyledProperty<double> GapProperty = AvaloniaProperty.Register<Group, double>(
"Gap", 10);
public double Gap
{
get => GetValue(GapProperty);
set => SetValue(GapProperty, value);
}
protected override Size MeasureOverride(Size availableSize)
{
return GroupUtils.Measure(availableSize, Children, Orientation, Gap);
}
protected override Size ArrangeOverride(Size finalSize)
{
return GroupUtils.ArrangeGroup(finalSize, Children, Orientation, Gap);
}
}
public class VGroup : Panel
{
public static readonly StyledProperty<double> GapProperty = AvaloniaProperty.Register<Group, double>(
"Gap", 10);
public double Gap
{
get => GetValue(GapProperty);
set => SetValue(GapProperty, value);
}
protected override Size MeasureOverride(Size availableSize)
{
return GroupUtils.Measure(availableSize, Children, Orientation.Vertical, Gap);
}
protected override Size ArrangeOverride(Size finalSize)
{
return GroupUtils.ArrangeGroup(finalSize, Children, Orientation.Vertical, Gap);
}
}
public class HGroup : Panel
{
public static readonly StyledProperty<double> GapProperty = AvaloniaProperty.Register<Group, double>(
"Gap", 10);
public double Gap
{
get => GetValue(GapProperty);
set => SetValue(GapProperty, value);
}
protected override Size MeasureOverride(Size availableSize)
{
return GroupUtils.Measure(availableSize, Children, Orientation.Horizontal, Gap);
}
protected override Size ArrangeOverride(Size finalSize)
{
return GroupUtils.ArrangeGroup(finalSize, Children, Orientation.Horizontal, Gap);
}
}
public static class GroupUtils
{
public static Size Measure(Size availableSize, Controls children, Orientation orientation, double gap)
{
Size layoutSlotSize = availableSize;
Size desiredSize = new Size();
bool hasVisibleChild = false;
//In order to handle percentwidth and percentheight scenario's we first have to measure all the children to determine their constrained measurement
//then depending on the orientation we factor in the left over space available and split that up via the percentages and orientation
//we use the measure with the true override to force the child to take our supplied size instead of it's default constrained size
var percentHeightChildrenMap = new Dictionary<Layoutable, double>();
var percentWidthChildrenMap = new Dictionary<Layoutable, double>();
//loop through all children and determine constrained size and check if percent height is set
for (int i = 0, count = children.Count; i < count; ++i)
{
// Get next child.
var child = children[i];
if (child == null) { continue; }
bool isVisible = child.IsVisible;
if (isVisible && !hasVisibleChild)
{
hasVisibleChild = true;
}
if (!double.IsNaN(child.PercentHeight))
{
percentHeightChildrenMap[child] = child.PercentHeight;
}
if (!double.IsNaN(child.PercentWidth))
{
percentWidthChildrenMap[child] = child.PercentWidth;
}
// Measure the child.
child.Measure(layoutSlotSize);
var childDesiredSize = child.DesiredSize;
if (orientation == Orientation.Vertical)
{
//in vertical mode, our width is the max width of the children
desiredSize = desiredSize.WithWidth(Math.Max(desiredSize.Width, childDesiredSize.Width));
//our height is the combine height of the children
desiredSize = desiredSize.WithHeight(desiredSize.Height + (isVisible ? gap : 0) + childDesiredSize.Height);
}
else
{
//in horizontal mode, our height is the max height of the children
desiredSize = desiredSize.WithHeight(Math.Max(desiredSize.Height, childDesiredSize.Height));
//our height is the combine width of the children
desiredSize = desiredSize.WithWidth(desiredSize.Width + (isVisible ? gap : 0) + childDesiredSize.Width);
}
}
if (orientation == Orientation.Vertical)
{
//Handle percent width
foreach (var child in children)
{
if (!double.IsNaN(child.PercentWidth))
{
child.InvalidateMeasure();
child.Measure(child.DesiredSize.WithWidth(child.PercentWidth * 0.01 * availableSize.Width), true);
desiredSize = desiredSize.WithWidth(Math.Max(desiredSize.Width, child.DesiredSize.Width));
}
}
//if we have dont have a visible child then set to 0, otherwise remove the last added gap
desiredSize = desiredSize.WithHeight(desiredSize.Height - (hasVisibleChild ? gap : 0));
if (hasVisibleChild && percentHeightChildrenMap.Count > 0)
{
//for those with percent height set, combine the percent heights together and if above 100, find the scale factor
var totalPercentHeight = percentHeightChildrenMap.Sum(v => v.Value);
totalPercentHeight = totalPercentHeight <= 0 ? 1 : totalPercentHeight;
var scaleRatio = 1 / (totalPercentHeight / 100);
//the available size leftover after the non-percent height children is now used to calculate the percentheight children sizes
var availableHeight = availableSize.Height - desiredSize.Height;
Debug.WriteLine($"Remapping %Height Children, availableHeight: {availableHeight}, scaleRatio: {scaleRatio}" );
foreach (var child in percentHeightChildrenMap.Keys)
{
var originalHeight = child.DesiredSize.Height;
var percentHeight = percentHeightChildrenMap[child];
var heightIncrease = availableHeight * percentHeight * scaleRatio * 0.01;
var recalculatedHeight = child.DesiredSize.Height + heightIncrease;
child.InvalidateMeasure();
child.Measure(child.DesiredSize.WithHeight(recalculatedHeight), true);
desiredSize = desiredSize.WithHeight(desiredSize.Height + child.DesiredSize.Height - originalHeight);
Debug.WriteLine($"$Found Child Height %:{percentHeight}, Original Height: {originalHeight}, New: {recalculatedHeight}" );
}
}
}
else
{
//Handle percent height
foreach (var child in children)
{
if (!double.IsNaN(child.PercentHeight))
{
child.InvalidateMeasure();
child.Measure(child.DesiredSize.WithHeight(child.PercentHeight * 0.01 * availableSize.Height), true);
desiredSize = desiredSize.WithHeight(Math.Max(desiredSize.Height, child.DesiredSize.Height));
}
}
//if we have dont have a visible child then set to 0, otherwise remove the last added gap
desiredSize = desiredSize.WithWidth(desiredSize.Width - (hasVisibleChild ? gap : 0));
if (hasVisibleChild && percentWidthChildrenMap.Count > 0)
{
//for those with percent Width set, combine the percent Widths together and if above 100, find the scale factor
var totalPercentWidth = percentWidthChildrenMap.Sum(v => v.Value);
totalPercentWidth = totalPercentWidth <= 0 ? 1 : totalPercentWidth;
var scaleRatio = 1 / (totalPercentWidth / 100);
//the available size leftover after the non-percent height children is now used to calculate the percentheight children sizes
var availableWidth = availableSize.Width - desiredSize.Width;
Debug.WriteLine($"Remapping %Width Children, availableWidth: {availableWidth}, scaleRatio: {scaleRatio}" );
foreach (var child in percentWidthChildrenMap.Keys)
{
var originalWidth = child.DesiredSize.Width;
var percentWidth = percentWidthChildrenMap[child];
var widthIncrease = availableWidth * percentWidth * scaleRatio * 0.01;
var recalculatedWidth = child.DesiredSize.Width + widthIncrease;
child.InvalidateMeasure();
child.Measure(child.DesiredSize.WithWidth(recalculatedWidth), true);
desiredSize = desiredSize.WithWidth(desiredSize.Width + child.DesiredSize.Width - originalWidth);
Debug.WriteLine($"$Found Child Width %:{percentWidth}, Original Width: {originalWidth}, New: {recalculatedWidth}" );
}
}
}
return desiredSize;
}
public static Size ArrangeGroup(Size finalSize, Controls children, Orientation orientation, double gap)
{
bool fHorizontal = (orientation == Orientation.Horizontal);
Rect rcChild = new Rect(finalSize);
double previousChildSize = 0.0;
var spacing = gap;
//
// Arrange and Position Children.
//
for (int i = 0, count = children.Count; i < count; ++i)
{
var child = children[i];
if (child == null || !child.IsVisible)
{
continue;
}
if (fHorizontal)
{
rcChild = rcChild.WithX(rcChild.X + previousChildSize);
previousChildSize = child.DesiredSize.Width;
rcChild = rcChild.WithWidth(previousChildSize);
rcChild = rcChild.WithHeight(child.DesiredSize.Height);
previousChildSize += spacing;
}
else
{
rcChild = rcChild.WithY(rcChild.Y + previousChildSize);
previousChildSize = child.DesiredSize.Height;
rcChild = rcChild.WithHeight(previousChildSize);
rcChild = rcChild.WithWidth(child.DesiredSize.Width);
previousChildSize += spacing;
}
child.Arrange(rcChild);
}
return finalSize;
}
}
Finally I had to make a change in the avalonia source class Layoutable
adding
public static readonly StyledProperty<double> PercentWidthProperty = AvaloniaProperty.Register<Layoutable, double>(
"PercentWidth", Double.NaN);
public static readonly StyledProperty<double> PercentHeightProperty = AvaloniaProperty.Register<Layoutable, double>(
"PercentHeight", Double.NaN);
public double PercentHeight
{
get => GetValue(PercentHeightProperty);
set => SetValue(PercentHeightProperty, value);
}
public double PercentWidth
{
get => GetValue(PercentWidthProperty);
set => SetValue(PercentWidthProperty, value);
}
Registering the properties in the constructor for layoutable such as
static Layoutable()
{
AffectsMeasure<Layoutable>(
WidthProperty,
HeightProperty,
MinWidthProperty,
MaxWidthProperty,
MinHeightProperty,
MaxHeightProperty,
MarginProperty,
**PercentHeightProperty,
PercentWidthProperty,**
HorizontalAlignmentProperty,
VerticalAlignmentProperty);
}
and modifying the measure method to accept a boolean 2nd parameter that tells the measure to use all available space and then uses the percentage calculation:
public void Measure(Size availableSize, bool useAvailable = false)
{
if (double.IsNaN(availableSize.Width) || double.IsNaN(availableSize.Height))
{
throw new InvalidOperationException("Cannot call Measure using a size with NaN values.");
}
if (!IsMeasureValid || _previousMeasure != availableSize)
{
var previousDesiredSize = DesiredSize;
var desiredSize = default(Size);
IsMeasureValid = true;
try
{
_measuring = true;
desiredSize = MeasureCore(availableSize);
//used in percentwidth height layout system
if (useAvailable == true)
{
desiredSize = desiredSize.WithHeight(Math.Max(availableSize.Height, desiredSize.Height))
.WithWidth(Math.Max(availableSize.Width, desiredSize.Width));
}
}
finally
{
_measuring = false;
}
if (IsInvalidSize(desiredSize))
{
throw new InvalidOperationException("Invalid size returned for Measure.");
}
DesiredSize = desiredSize;
_previousMeasure = availableSize;
Logger.TryGet(LogEventLevel.Verbose, LogArea.Layout)?.Log(this, "Measure requested {DesiredSize}", DesiredSize);
if (DesiredSize != previousDesiredSize)
{
this.GetVisualParent<Layoutable>()?.ChildDesiredSizeChanged(this);
}
}
}
I'd suggest reading the documentation when picking up a new UI tech. The worst thing you can do is try to bend a new technology to the way another unrelated technology works.
Particularly when what you need already exists.
50% / 50% columns ...
<Grid ColumnDefinitions="1*, 1*">
<Border Grid.Column="0" Background="Red" />
<Border Grid.Column="1" Background="Blue" />
</Grid>
25% / 75%
<Grid ColumnDefinitions="1*, 3*">
<Border Grid.Column="0" Background="Red" />
<Border Grid.Column="1" Background="Blue" />
</Grid>
You typically don't set heights and widths on controls. You define the space they have on the UI and allow them to adapt. Some controls might have a default height in a style that's applied globally.
Try to think in XAML terms when using XAML and Adobe terms when using Adobe. Mixing the two will self-inflict a lot of pain.
I'd advise anyone else finding this question to not use this percentage approach.
I have code as per attached that builds a Xamrin Grid virtually in code. The problem that I have not been able to resolve is Events that will be triggered when entering data into a cell.
Here is the output:
Output 1
Output 2
The purpose of this post is to create an event that can be invoked to take action when data is entered into any of the cells created at runtime in the grid.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="VarGridSample.MainPage">
<ScrollView>
<StackLayout
Spacing="25"
Padding="30,0"
VerticalOptions="Center">
<Label
Text="Variable Grid"
FontSize="32"
HorizontalOptions="Center" />
<Label
Text="I am attempting to build a Grid at runtime to allow for variable rows and columns. The Grid has been initially defined in the Content Page. The Grid is built in the code based on the number of rows and columns requested. Entry controls are built for each cell. The problem is how to build the Events for each cell...ANY IDEAS?"
FontSize="18"
HorizontalOptions="Center" />
<Entry x:Name="Rows"
Text="{Binding Rows }"
HorizontalOptions="Center" Placeholder="Enter Total Rows" WidthRequest="150" />
<Entry x:Name="Cols"
Text="{Binding Rows }"
HorizontalOptions="Center" Placeholder="Enter Total Columns" WidthRequest="150" />
<Button x:Name="BuildGrid"
Text="Build Grid" HorizontalOptions="Center" Clicked="BuildGrid_Clicked" />
<Grid x:Name="VarGrid">
</Grid>
</StackLayout>
</ScrollView>
</ContentPage>
Below is the C# code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace VarGridSample
{
public partial class MainPage : ContentPage
{
private int rows { get; set; }
private int cols { get; set; }
private int cellcntr = 0;
public MainPage()
{
InitializeComponent();
}
private void BuildGrid_Clicked(object sender, EventArgs e)
{
rows = Int32.Parse(Rows.Text);
cols = Int32.Parse(Cols.Text);
for (int rowIndex = 0; rowIndex < rows; rowIndex++)
{
VarGrid.RowDefinitions.Add(new RowDefinition());
}
for (int rowIndex = 0; rowIndex < rows; rowIndex++)
{
for (int colIndex = 0; colIndex < cols; colIndex++)
{
var entry = new Entry
{
Text = "cell" + (cellcntr).ToString(),
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.Center
};
VarGrid.Children.Add(entry, colIndex, rowIndex);
cellcntr++;
}
}
}
}
}
You can add this to MainPage.xaml.cs:
private void BuildGrid_Clicked(object sender, EventArgs e)
{
rows = Int32.Parse(Rows.Text);
cols = Int32.Parse(Cols.Text);
for (int rowIndex = 0; rowIndex < rows; rowIndex++)
{
VarGrid.RowDefinitions.Add(new RowDefinition());
}
for (int rowIndex = 0; rowIndex < rows; rowIndex++)
{
for (int colIndex = 0; colIndex < cols; colIndex++)
{
var entry = new Entry
{
Text = "cell" + (cellcntr).ToString(),
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.Center,
};
//add the event
entry.TextChanged += Entry_TextChanged;
VarGrid.Children.Add(entry, colIndex, rowIndex);
cellcntr++;
}
}
}
private void Entry_TextChanged(object sender, TextChangedEventArgs e)
{
Console.WriteLine(e.OldTextValue);
var entry = (Entry)sender;
var parent= (Grid)entry.Parent;
Console.WriteLine(parent.Children.Count);
}
I was trying to make daily calendar and I create this:
xaml:
<Page
x:Class="Calendar.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Calendar"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Pivot x:Name="CalendarPivot" Title="Pivot" SelectionChanged="CalendarPivot_SelectionChanged">
</Pivot>
</Grid>
<Page.BottomAppBar>
<CommandBar>
<AppBarButton x:Name="TodayAppBarButton" Label="dziś" Click="TodayAppBarButton_Click">
<AppBarButton.Icon>
<FontIcon x:Name="TodayFontIcon" Glyph="" FontSize="10" FontFamily="Segoe WP"/>
</AppBarButton.Icon>
</AppBarButton>
<AppBarButton x:Name="ChooseDateAppBarButton" Label="wybierz" Icon="Calendar" Click="ChooseDateAppBarButton_Click">
<FlyoutBase.AttachedFlyout>
<DatePickerFlyout DatePicked="DatePickerFlyout_DatePicked"/>
</FlyoutBase.AttachedFlyout>
</AppBarButton>
</CommandBar>
</Page.BottomAppBar>
c#:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Navigation;
namespace Calendar
{
public sealed partial class MainPage : Page
{
bool pivotClear = false;
public MainPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var monthDatefmt = new Windows.Globalization.DateTimeFormatting.DateTimeFormatter("month.abbreviated");
SetCalendar(DateTimeOffset.Now);
TodayFontIcon.Glyph = DateTimeOffset.Now.Day + " " + monthDatefmt.Format(DateTimeOffset.Now);
}
private void SetCalendar(DateTimeOffset startDate)
{
var loader = new Windows.ApplicationModel.Resources.ResourceLoader();
var dayOfWeekDatefmt = new Windows.Globalization.DateTimeFormatting.DateTimeFormatter("dayofweek.full");
var dayMonthYearDatefmt = new Windows.Globalization.DateTimeFormatting.DateTimeFormatter("day month year");
for (int i = -4; i < 5; i++)
{
PivotItem pivotItem = new PivotItem();
DateTimeOffset date = new DateTimeOffset();
date = startDate.AddDays(i);
pivotItem.Tag = date.Month + "/" + date.Day + "/" + date.Year;
if (date.Date == DateTimeOffset.Now.Date)
{
pivotItem.Header = loader.GetString("Today");
}
else
{
pivotItem.Header = dayOfWeekDatefmt.Format(date).ToLower();
}
CalendarPivot.Items.Add(pivotItem);
}
CalendarPivot.SelectedIndex = 4;
CalendarPivot.Title = dayMonthYearDatefmt.Format(startDate).ToUpper();
}
private void CalendarPivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var loader = new Windows.ApplicationModel.Resources.ResourceLoader();
Debug.WriteLine("change started" + CalendarPivot.SelectedIndex);
if (!pivotClear)
{
var sdatefmt = new Windows.Globalization.DateTimeFormatting.DateTimeFormatter("dayofweek.full");
var dayMonthYearDatefmt = new Windows.Globalization.DateTimeFormatting.DateTimeFormatter("day month year");
PivotItem selectedPivotItem = CalendarPivot.SelectedItem as PivotItem;
DateTimeOffset selectedPivotItemDate = new DateTimeOffset();
selectedPivotItemDate = DateTimeOffset.Parse(selectedPivotItem.Tag as string);
CalendarPivot.Title = dayMonthYearDatefmt.Format(selectedPivotItemDate).ToUpper();
PivotItem lastPivotItem = CalendarPivot.Items.Last() as PivotItem;
DateTimeOffset lastPivotItemDate = new DateTimeOffset();
lastPivotItemDate = DateTimeOffset.Parse(lastPivotItem.Tag as string);
if (selectedPivotItemDate.Date >= lastPivotItemDate.Date.AddDays(-3))
{
PivotItem pivotItem = new PivotItem();
DateTimeOffset date = new DateTimeOffset();
date = lastPivotItemDate.AddDays(1);
pivotItem.Tag = date.Month + "/" + date.Day + "/" + date.Year;
if (date.Date == DateTimeOffset.Now.Date)
{
pivotItem.Header = loader.GetString("Today");
}
else
{
pivotItem.Header = sdatefmt.Format(date).ToLower();
}
CalendarPivot.Items.Add(pivotItem);
}
PivotItem firstPivotItem = CalendarPivot.Items.First() as PivotItem;
DateTimeOffset firstPivotItemDate = new DateTimeOffset();
firstPivotItemDate = DateTimeOffset.Parse(firstPivotItem.Tag as string);
if (selectedPivotItemDate.Date <= firstPivotItemDate.Date.AddDays(3))
{
PivotItem pivotItem = new PivotItem();
DateTimeOffset date = new DateTimeOffset();
date = firstPivotItemDate.AddDays(-1);
pivotItem.Tag = date.Month + "/" + date.Day + "/" + date.Year;
if (date.Date == DateTimeOffset.Now.Date)
{
pivotItem.Header = "dziś";
}
else
{
pivotItem.Header = sdatefmt.Format(date).ToLower();
}
CalendarPivot.Items.Insert(0, pivotItem);
}
}
Debug.WriteLine("change ended" + CalendarPivot.SelectedIndex);
}
private void TodayAppBarButton_Click(object sender, RoutedEventArgs e)
{
pivotClear = true;
CalendarPivot.Items.Clear();
SetCalendar(DateTimeOffset.Now);
pivotClear = false;
}
private void ChooseDateAppBarButton_Click(object sender, RoutedEventArgs e)
{
FlyoutBase.ShowAttachedFlyout((FrameworkElement)sender);
}
private void DatePickerFlyout_DatePicked(DatePickerFlyout sender, DatePickedEventArgs args)
{
pivotClear = true;
DateTimeOffset date = args.NewDate;
CalendarPivot.Items.Clear();
SetCalendar(date);
pivotClear = false;
}
}
}
It works well when I change days forward, but I have one heavyweight bug in backward way. I will try to explain it in steps.
Today is monday and user changes it to previous day.
Pivot's selected item changes from 4(monday) to 3(sunday) .
System decects that it must generate one more item to pivot.
System generates new item and inserts it in 0 position.
Pivot's selected item changes from 3 to 4(now sunday).
Pivot still shows item 3 which is saturday now, and it's looks like calendar skipped one day.
I would be very grateful if someone knows how to fix it.
EDIT :
I made change based on Nate Diamond suggestion and it works for now well:
Here, have a code:
XAML:
<Page
x:Class="LetsMakeANewCalendar.NewCalendar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:LetsMakeANewCalendar"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Pivot x:Name="CalendarPivot" Title="" SelectionChanged="CalendarPivot_SelectionChanged">
<PivotItem x:Name="PivotItem0"/>
<PivotItem x:Name="PivotItem1"/>
<PivotItem x:Name="PivotItem2"/>
<PivotItem x:Name="PivotItem3"/>
<PivotItem x:Name="PivotItem4"/>
<PivotItem x:Name="PivotItem5"/>
<PivotItem x:Name="PivotItem6"/>
</Pivot>
</Grid>
<Page.BottomAppBar>
<CommandBar>
<AppBarButton x:Name="TodayAppBarButton" Label="dziś" Click="TodayAppBarButton_Click">
<AppBarButton.Icon>
<FontIcon x:Name="TodayFontIcon" Glyph="" FontSize="10" FontFamily="Segoe WP"/>
</AppBarButton.Icon>
</AppBarButton>
<AppBarButton x:Name="ChooseDateAppBarButton" Label="wybierz" Icon="Calendar" Click="ChooseDateAppBarButton_Click">
<FlyoutBase.AttachedFlyout>
<DatePickerFlyout DatePicked="DatePickerFlyout_DatePicked"/>
</FlyoutBase.AttachedFlyout>
</AppBarButton>
</CommandBar>
</Page.BottomAppBar>
C#:
public sealed partial class NewCalendar : Page
{
int pivotIndex;
DateTimeOffset previousDate = new DateTimeOffset();
public NewCalendar()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var monthDatefmt = new Windows.Globalization.DateTimeFormatting.DateTimeFormatter("month.abbreviated");
SetCalendar(DateTimeOffset.Now);
TodayFontIcon.Glyph = DateTimeOffset.Now.Day + " " + monthDatefmt.Format(DateTimeOffset.Now);
}
private void SetCalendar(DateTimeOffset startDate)
{
var loader = new Windows.ApplicationModel.Resources.ResourceLoader();
var dayOfWeekDatefmt = new Windows.Globalization.DateTimeFormatting.DateTimeFormatter("dayofweek.full");
var dayMonthYearDatefmt = new Windows.Globalization.DateTimeFormatting.DateTimeFormatter("day month year");
int numberOfDay = GetNumberOfDay(startDate);
int i = 0;
foreach (PivotItem item in CalendarPivot.Items)
{
DateTimeOffset date = new DateTimeOffset();
date = startDate.AddDays(i - numberOfDay);
item.Tag = date.Month + "/" + date.Day + "/" + date.Year;
item.Header = dayOfWeekDatefmt.Format(date).ToLower();
i++;
}
CalendarPivot.SelectedIndex = pivotIndex = numberOfDay;
}
private int GetNumberOfDay(DateTimeOffset date)
{
int numberOfDay;
switch (date.DayOfWeek)
{
case DayOfWeek.Monday:
numberOfDay = 0;
break;
case DayOfWeek.Tuesday:
numberOfDay = 1;
break;
case DayOfWeek.Wednesday:
numberOfDay = 2;
break;
case DayOfWeek.Thursday:
numberOfDay = 3;
break;
case DayOfWeek.Friday:
numberOfDay = 4;
break;
case DayOfWeek.Saturday:
numberOfDay = 5;
break;
case DayOfWeek.Sunday:
numberOfDay = 6;
break;
default:
numberOfDay = -1;
break;
}
return numberOfDay;
}
private void CalendarPivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var loader = new Windows.ApplicationModel.Resources.ResourceLoader();
Debug.WriteLine("old pivot index " + pivotIndex);
Debug.WriteLine("change started " + CalendarPivot.SelectedIndex);
var dayMonthYearDatefmt = new Windows.Globalization.DateTimeFormatting.DateTimeFormatter("day month year");
if ((CalendarPivot.SelectedIndex == 6) && (pivotIndex == 0))
{
SetCalendar(previousDate.AddDays(-1));
}
else if ((CalendarPivot.SelectedIndex == 0) && (pivotIndex == 6))
{
SetCalendar(previousDate.AddDays(1));
}
PivotItem selectedPivotItem = CalendarPivot.SelectedItem as PivotItem;
DateTimeOffset selectedPivotItemDate = new DateTimeOffset();
selectedPivotItemDate = DateTimeOffset.Parse(selectedPivotItem.Tag as string);
previousDate = selectedPivotItemDate.Date;
pivotIndex = CalendarPivot.SelectedIndex;
CalendarPivot.Title = dayMonthYearDatefmt.Format(selectedPivotItemDate).ToUpper();
if (selectedPivotItemDate.Date == DateTimeOffset.Now.Date)
{
CalendarPivot.Title += " - " + loader.GetString("Today").ToUpper();
}
Debug.WriteLine("change ended " + CalendarPivot.SelectedIndex);
Debug.WriteLine("new pivot index " + pivotIndex);
}
private void TodayAppBarButton_Click(object sender, RoutedEventArgs e)
{
var loader = new Windows.ApplicationModel.Resources.ResourceLoader();
var dayMonthYearDatefmt = new Windows.Globalization.DateTimeFormatting.DateTimeFormatter("day month year");
SetCalendar(DateTimeOffset.Now);
PivotItem selectedPivotItem = CalendarPivot.SelectedItem as PivotItem;
DateTimeOffset selectedPivotItemDate = new DateTimeOffset();
selectedPivotItemDate = DateTimeOffset.Parse(selectedPivotItem.Tag as string);
previousDate = selectedPivotItemDate.Date;
pivotIndex = CalendarPivot.SelectedIndex;
CalendarPivot.Title = dayMonthYearDatefmt.Format(selectedPivotItemDate).ToUpper() + " - " + loader.GetString("Today").ToUpper();
}
private void ChooseDateAppBarButton_Click(object sender, RoutedEventArgs e)
{
FlyoutBase.ShowAttachedFlyout((FrameworkElement)sender);
}
private void DatePickerFlyout_DatePicked(DatePickerFlyout sender, DatePickedEventArgs args)
{
var loader = new Windows.ApplicationModel.Resources.ResourceLoader();
var dayMonthYearDatefmt = new Windows.Globalization.DateTimeFormatting.DateTimeFormatter("day month year");
DateTimeOffset date = args.NewDate;
SetCalendar(date);
PivotItem selectedPivotItem = CalendarPivot.SelectedItem as PivotItem;
DateTimeOffset selectedPivotItemDate = new DateTimeOffset();
selectedPivotItemDate = DateTimeOffset.Parse(selectedPivotItem.Tag as string);
previousDate = selectedPivotItemDate.Date;
pivotIndex = CalendarPivot.SelectedIndex;
CalendarPivot.Title = dayMonthYearDatefmt.Format(selectedPivotItemDate).ToUpper();
if (selectedPivotItemDate.Date == DateTimeOffset.Now.Date)
{
CalendarPivot.Title += " - " + loader.GetString("Today").ToUpper();
}
}
}
In my Windows 8 app, I am trying to change the text of a textblock inside a DataTemplate of a FlipView...
my FlipView datatemplate (simplified...) :
<FlipView.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="test" />
</DataTemplate>
</FlipView.ItemTemplate>
I tried this solution : How do I access a control inside a XAML DataTemplate?
So my .cs :
var _Container = flipView.ItemContainerGenerator.ContainerFromItem(flipView.SelectedItem);
var _Children = AllChildren(_Container);
var myTextBlock= _Children.OfType<TextBlock>().FirstOrDefault(c => c.Name.Equals("test"));
myTextBlock.Text = "test";
with the method :
public List<Control> AllChildren(DependencyObject parent)
{
var _List = new List<Control>();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var _Child = VisualTreeHelper.GetChild(parent, i);
if (_Child is Control)
_List.Add(_Child as Control);
_List.AddRange(AllChildren(_Child));
}
return _List;
}
But I get a NullReferenceException error : "{"Object reference not set to an instance of an object."}"
So it doesn't find my textblock...
Thanks
hello friend i have checked your code..and what i found is a very unnoticeable mistake..that is about the Control keyword..actually it is your type of control you want to search in your flipview..like textblock,textbox etc...you have to just change your AllChilderen Function like this and then all will work fine..
public List<TextBlock> AllChildren(DependencyObject parent)
{
var _List = new List<TextBlock>();
int j = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var _Child = VisualTreeHelper.GetChild(parent, i);
if (_Child is TextBlock)
_List.Add(_Child as TextBlock);
_List.AddRange(AllChildren(_Child));
}
return _List;
}
hope it will help you..
I am writing a Silverlight 4 business application and have run into an issue. I need the text input in TextBoxes to be forced to UpperCase. What I understand from various forums is Silverlight does not Implement CharacterCasing and CSS Styling.
Is there any other way to achieve this?
You can achieve this by creating a behavior, like this:
public class UpperCaseAction : TriggerAction<TextBox>
{
protected override void Invoke(object parameter)
{
var selectionStart = AssociatedObject.SelectionStart;
var selectionLenght = AssociatedObject.SelectionLength;
AssociatedObject.Text = AssociatedObject.Text.ToUpper();
AssociatedObject.SelectionStart = selectionStart;
AssociatedObject.SelectionLength = selectionLenght;
}
}
Then, use it in your TextBox, like this:
<Grid x:Name="LayoutRoot" Background="White">
<TextBox TextWrapping="Wrap" VerticalAlignment="Top" Margin="10">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<ASD_Answer009_Behaviors:UpperCaseAction/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
</Grid>
Where i: is a namespace for
clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity
Code behind:
System.Windows.Interactivity.EventTrigger eventTrigger = new System.Windows.Interactivity.EventTrigger("TextChanged");
eventTrigger.Actions.Add(new UpperCaseAction());
System.Windows.Interactivity.Interaction.GetTriggers(myTextBox).Add(eventTrigger);
In order to create and use behaviors, you need to download and install the Expression Blend SDK for Silverlight 4 and add a reference to System.Windows.Interactivity.dll.
Try this:
private void txt2_KeyDown(object sender, KeyEventArgs e)
{
e.Handled = MakeUpperCase((TextBox)sender, e);
}
bool MakeUpperCase(TextBox txt, KeyEventArgs e)
{
if (Keyboard.Modifiers != ModifierKeys.None || (e.Key < Key.A) || (e.Key > Key.Z)) //do not handle ModifierKeys (work for shift key)
{
return false;
}
else
{
string n = new string(new char[] { (char)e.PlatformKeyCode });
int nSelStart = txt.SelectionStart;
txt.Text = txt.Text.Remove(nSelStart, txt.SelectionLength); //remove character from the start to end selection
txt.Text = txt.Text.Insert(nSelStart, n); //insert value n
txt.Select(nSelStart + 1, 0); //for cursortext
return true; //stop to write in txt2
}
}
private void txt2_KeyDown(object sender, KeyEventArgs e)
{
if (Keyboard.Modifiers != ModifierKeys.None) return; //do not handle ModifierKeys (work for shift key)
string n = new string(new char[] { (char)e.PlatformKeyCode });
int nSelStart = txt2.SelectionStart;
txt2.Text = txt2.Text.Remove(nSelStart, txt2.SelectionLength); //remove character from the start to end selection
txt2.Text = txt2.Text.Insert(nSelStart, n); //insert value n
txt2.Select(nSelStart + 1, 0); //for cursortext
e.Handled = true; //stop to write in txt2
}