How do I write the code that flows items in a GridView (XAML-Win8) group, according to the below illustration?
I currently have a custom TemplateSelector that selects a different (larger) template for the first item, but the flow as specified here:
<GroupStyle.Panel>
<ItemsPanelTemplate>
<q42:WrapPanel Orientation="Horizontal" Width="440" Margin="0,0,80,0"/>
<!-- also tried VariableSizedWrapGrid -->
</ItemsPanelTemplate>
</GroupStyle.Panel>
Wraps items 1 through 3 similarly, but then places item 4 at item 6's position, without filling out items 4 nor 5.
Question becomes; how do I write code that acts similar to css:
.item { display: inline-block; }
.item1 { float: left; }
, which would make the items flow like I want?
Andreas Hammar linked me to a working solution:
using System.Collections.Generic;
using Application1.Data;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace Application1
{
public class MyGridView : GridView
{
int _rowVal;
int _colVal;
readonly List<Size> _sequence;
public MyGridView()
{
_sequence = new List<Size>
{
LayoutSizes.PrimaryItem,
LayoutSizes.SecondarySmallItem,
LayoutSizes.SecondarySmallItem,
LayoutSizes.OtherSmallItem,
LayoutSizes.OtherSmallItem, // 5
LayoutSizes.OtherSmallItem,
LayoutSizes.SecondaryTallItem, // 7
LayoutSizes.OtherSmallItem,
LayoutSizes.SecondarySmallItem, // 9
LayoutSizes.OtherSmallItem,
LayoutSizes.SecondarySmallItem, // 11
LayoutSizes.SecondarySmallItem,
LayoutSizes.OtherSmallItem,
LayoutSizes.OtherSmallItem
};
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
var dataItem = item as SampleDataItem;
var index = -1;
if (dataItem != null)
{
index = dataItem.Group.Items.IndexOf(dataItem);
}
if (index >= 0 && index < _sequence.Count)
{
_colVal = (int) _sequence[index].Width;
_rowVal = (int) _sequence[index].Height;
}
else
{
_colVal = (int) LayoutSizes.OtherSmallItem.Width;
_rowVal = (int) LayoutSizes.OtherSmallItem.Height;
}
VariableSizedWrapGrid.SetRowSpan(element as UIElement, _rowVal);
VariableSizedWrapGrid.SetColumnSpan(element as UIElement, _colVal);
}
}
public static class LayoutSizes
{
public static Size PrimaryItem = new Size(6, 2);
public static Size SecondarySmallItem = new Size(3, 1);
public static Size SecondaryTallItem = new Size(2, 2);
public static Size OtherSmallItem = new Size(2, 1);
}
}
<local:MyGridView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</local:MyGridView.ItemsPanel>
<local:MyGridView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Margin="1,0,0,6">
<Button
AutomationProperties.Name="Group Title"
Content="{Binding Title}"
Click="Header_Click"
Style="{StaticResource TextButtonStyle}"/>
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid ItemWidth="80" ItemHeight="160" Orientation="Vertical" Margin="0,0,80,0" MaximumRowsOrColumns="3"/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</local:MyGridView.GroupStyle>
</local:MyGridView>
I think it could be done with a VSWG.
I've gotten halfway there but haven't hade time to finish it...
First you need to set the attached prop VariableSizedWrapGrid.RowSpan and ColSpan - and that has to be set on the item container, by inheriting the VSWG:
http://blogs.u2u.be/diederik/post/2012/03/07/Databinding-to-the-VariableSizedWrapGrid-in-Windows-8-Metro.aspx
And in your case 2x2 for the first item, 1x1 on the rest.
The measuring of cell size is done on the first element, unless you specify the ItemHeight etc explicitly. So you have to accomplish this somehow :)
http://social.msdn.microsoft.com/Forums/en-US/winappswithcsharp/thread/569e048a-9f5e-4fb3-870a-380de5906e80
WG supports single sized items and all items are shown at that same size. VSWG allows for variable sized items but the item sizes allowed are in integral multiples of the base cell size. WG and VSWG work in layout cells. The size of the layout cell is determined by ItemHeight and ItemWidth properties. If these properties are not set, then the size of the first item is used as the cell size and subsequent items are measured at that size for WG; for VSWG the item is measured in integral multiplication of the cell size based on RowSpan and ColumnSpan properties.
It seems that you have to set the height and width of the VSWG in order to accomodate the size of the largest item if you don't want the item to be first in the list.
--> this is the part that I did not get around to.
Lastly the horizontal orientation.
Good luck!
Related
I am trying to hide last Grid column. I'm trying to do it with DataTrigger, this is how my trigger looks like:
<ResourceDictionary>
<Style x:Key="HideLastVerticalLine" TargetType="BoxView">
<Style.Triggers>
<DataTrigger
Binding="{Binding Items, Path=Items.LastOrDefault}"
TargetType="BoxView"
Value="{Binding Items.Length}">
<Setter Property="IsVisible" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
<BoxView Style="{StaticResource HideLastVerticalLine}" Grid.Column="1" HeightRequest="100" WidthRequest="1" BackgroundColor="Black"/>
I'm applying this DataTrigger to a BoxView, which contains a vertical line separator (I want something as Trim(), just to remove last separator line.
How can I do it?
You can use a DataTemplateSelector to achieve this.
The sample is here.
Create two DataTemplates, one for LastViewCell and one for other ViewCells:
public class PersonDataTemplateSelector : DataTemplateSelector
{
public DataTemplate NormalTemplate { get; set; }
public DataTemplate LastCellTemplate { get; set; }
protected override DataTemplate OnSelectTemplate (object item, BindableObject container)
{
var lastItem = Items.LastOrDefault();
return lastItem = item ? LastCellTemplate : NormalTemplate;
}
}
Choose to use which DataTemplate by checking if the item is last Item.
Create Bindable Layout.
Create separate template selector class.
The class used to populate the bindable layout itemsource must have new property isLast bool.
Update the list with last element as true.
Now Bind this new list with the bindable layout you are using on the view.
Don’t forget to add your templateselector on resource dictionary of that page.
For updating last item in list to isLast = true use.
int index = 0;
foreach (var item in list) // here updating last element of list islast =true
{
item.IsLast = (index == list.Count - 1);
index++;
}
I am making a Gridview that scrolls behind a title element (which is semi transparent to show the items being scrolled behind it). To do this, I have layered the Grid containing the title and the GridView by placing them both as children in the same Grid.
<Grid>
<GridView>
<!-- Stuff -->
<GridView>
<Grid Height="100">
<!-- Title Content here -->
</Grid>
</Grid>
This works fine, but causes the GridView to display elements initially behind the title. To fix this, I offset the ItemsWrapGrid in the GridView:
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsWrapGrid Name="ItemsWrapGrid"
Margin="0,100,0,0"
Orientation="Horizontal"
HorizontalAlignment="Center"></ItemsWrapGrid>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
Now the items start as if they are below the title content, and scroll underneath it.
The only remaining problem is the scrollbar for the GridView. The scrollbar still goes to the top of the parent Grid, which means it goes behind the title, even though the items in the GridView themselves begin below the title. This is particularly an issue when there are enough items in the GridView (rows) to cause the scrollbar to be small enough that it is entirely behind the title.
Is there a way to offset the scrollbar similar to the GridView items? Is this the wrong approach?
UWP Offset GridView Scrollbar
For your requirement, you could use VisualTreeHelper to get VerticalScrollBar element, then set Margin = 0,100,0,0 in the GridView load event handler. For detail steps please refer the following code.
public static DependencyObject MyFindGridViewChildByName(DependencyObject parant, string ControlName)
{
int count = VisualTreeHelper.GetChildrenCount(parant);
for (int i = 0; i < count; i++)
{
var MyChild = VisualTreeHelper.GetChild(parant, i);
if (MyChild is FrameworkElement && ((FrameworkElement)MyChild).Name == ControlName)
return MyChild;
var FindResult = MyFindGridViewChildByName(MyChild, ControlName);
if (FindResult != null)
return FindResult;
}
return null;
}
private void TestGridView_Loaded(object sender, RoutedEventArgs e)
{
var scrollBar = MyFindGridViewChildByName(TestGridView, "VerticalScrollBar");
scrollBar.SetValue(MarginProperty, new Thickness(0, 100, 0, 0));
}
I have a Popup which will fill the whole page when opened.
<Grid x:Name="gridRoot" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Button Content="Open" HorizontalAlignment="Center" Click="{x:Bind viewModel.OpenPopup}" />
<Popup x:Name="popupCorrect" VerticalAlignment="Top" IsOpen="{Binding IsOpen}" IsLightDismissEnabled="False">
<Popup.ChildTransitions>
<TransitionCollection>
<PaneThemeTransition Edge="Left" />
</TransitionCollection>
</Popup.ChildTransitions>
<uc:MyPopup Width="{Binding ElementName=gridRoot, Path=ActualWidth}" Height="{Binding ElementName=gridRoot, Path=ActualHeight}"/>
</Popup>
</Grid>
The Popup is a UserControl
<Grid Background="Red">
<Button Content="Close" HorizontalAlignment="Center" Click="{x:Bind viewModel.ClosePopup}" />
</Grid>
The page
When popup is shown
Close the popup, resize the page, then reopen the popup. Notice that it does not match the new size of container page even though its Width and Height is bound to gridRoot . Do I have to manually set a new Width and Height for the popup? Why can't I achieve this with binding? This issue also appears on mobile during 'OrientationChanged'
Based on Decade Moon comment, this is how to resize the popup to match the parent container as its size changed.
Create a dependency property in the code behind
public double PageWidth
{
get { return (double)GetValue(PageWidthProperty); }
set { SetValue(PageWidthProperty, value); }
}
public static readonly DependencyProperty PageWidthProperty =
DependencyProperty.Register("PageWidth", typeof(double), typeof(GamePage), new PropertyMetadata(0d));
public double PageHeight
{
get { return (double)GetValue(PageHeightProperty); }
set { SetValue(PageHeightProperty, value); }
}
public static readonly DependencyProperty PageHeightProperty =
DependencyProperty.Register("PageHeight", typeof(double), typeof(GamePage), new PropertyMetadata(0d));
Update the value on SizeChanged event
private void GamePage_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (e.NewSize.Width > 0d && e.NewSize.Height > 0d)
{
PageWidth = e.NewSize.Width;
PageHeight = e.NewSize.Height;
}
}
Then in XAML, just use x:Bind to bind the popup width and height
<Popup x:Name="popupCorrect" VerticalAlignment="Top" IsOpen="{Binding IsPopupCorrectOpen, Mode=TwoWay}" IsLightDismissEnabled="False">
<Popup.ChildTransitions>
<TransitionCollection>
<PaneThemeTransition Edge="Left" />
</TransitionCollection>
</Popup.ChildTransitions>
<uc:PopupCorrect Width="{x:Bind PageWidth, Mode=TwoWay}" Height="{x:Bind PageHeight, Mode=TwoWay}"/>
</Popup>
Pretty straight forward. Just remember not to use the ActualWidth or ActualHeight properties for binding source as they do not raise the PropertyChanged event.
Although it has an ActualWidthProperty backing field, ActualWidth does not raise property change notifications and it should be thought of as a regular CLR property and not a dependency property.
For purposes of ElementName binding, ActualWidth does not post updates when it changes (due to its asynchronous and run-time calculated nature). Do not attempt to use ActualWidth as a binding source for an ElementName binding. If you have a scenario that requires updates based on ActualWidth, use a SizeChanged handler.
#PutraKg have a great way.
But I have two way to solve it.
The first is set the VerticalAlignment="Center" HorizontalAlignment="Center" that can make the popup in the center.
But I think youare not content to put it in the center.
The great way is use the screen position.
You can get the Grid's screen postion and make it to popup.
In open button
private void Button_OnClick(object sender, RoutedEventArgs e)
{
var grid = (UIElement)popupCorrect.Parent; //get grid
var p = grid.TransformToVisual (Window.Current.Content).TransformPoint(new Point(0, 0)); //get point
popupCorrect.HorizontalOffset = p.X;
popupCorrect.VerticalOffset = p.Y;
popupCorrect.IsOpen = !popupCorrect.IsOpen;
}
I need to create UI as following (kind of a tag cloud control)
n number of buttons (Count will be determined during runtime)
Each button will have different width based on its content
Max 3 buttons in a row
I have tried following things
VariableSizedWrapGrid as ItemsPanel of GridView (It requires RowSpan
and ColumnSpan to be specified which is not feasible as width is
determined during runtime)
StackPanel with Horizontal orientation (All the buttons were arranged in one line)
Any thoughts?
Here is a great blogpost on how to create a WrapPanel based on your requirements.
I have created a TagsPanel based off of this blog to use in one of my app. Key is to loop through to items and get the actual size and set it based on available width of the panel. See below.
public class TagsPanel : Panel
{
protected override Size MeasureOverride(Size availableSize)
{
// Just take up all of the width
Size finalSize = new Size { Width = availableSize.Width };
double x = 0;
double rowHeight = 0d;
foreach (var child in Children)
{
// Tell the child control to determine the size needed
child.Measure(availableSize);
x += child.DesiredSize.Width;
if (x > availableSize.Width)
{
// this item will start the next row
x = child.DesiredSize.Width;
// adjust the height of the panel
finalSize.Height += rowHeight;
rowHeight = child.DesiredSize.Height;
}
else
{
// Get the tallest item
rowHeight = Math.Max(child.DesiredSize.Height, rowHeight);
}
}
// Add the final height
finalSize.Height += rowHeight;
return finalSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
Rect finalRect = new Rect(0, 0, finalSize.Width, finalSize.Height);
double rowHeight = 0;
foreach (var child in Children)
{
if ((child.DesiredSize.Width + finalRect.X) > finalSize.Width)
{
// next row!
finalRect.X = 0;
finalRect.Y += rowHeight;
rowHeight = 0;
}
// Place the item
child.Arrange(new Rect(finalRect.X, finalRect.Y, child.DesiredSize.Width, child.DesiredSize.Height));
// adjust the location for the next items
finalRect.X += child.DesiredSize.Width;
rowHeight = Math.Max(child.DesiredSize.Height, rowHeight);
}
return finalSize;
}
}
and use it in a GridView like below.
<GridView ItemsSource="{Binding tags}" >
<GridView.ItemTemplate>
<DataTemplate>
<Button Content="{Binding ''}" Margin="5"/>
</DataTemplate>
</GridView.ItemTemplate>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<local:TagsPanel/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
</GridView>
Basically, VariableSizedWrapGrid treat all its childs with equal size. You can use ColumnSpan and RowSpan to extend the area of child.
Another way is to create a WrapPanel user control and then use in place of VariableSizedWrapGrid. This will shape output as you described.
Using Caliburn.Micro for a WinRT application, I would like to control the ZIndex of items displays in an ItemsControl.
When a user taps on an item, it should become the topmost element.
<ItemsControl Background="White" Height="auto" Width="auto" x:Name="Parts"
HorizontalAlignment="Left"
VerticalAlignment="Top"
>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas></Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
The viewmodel bound to the view above contains a property Parts:
private BindableCollection<IPartViewModel> _parts = new BindableCollection<IPartViewModel>();
public BindableCollection<IPartViewModel> Parts
{
get { return _parts; }
set { _parts = value; NotifyOfPropertyChange(() => Parts); }
}
IPartViewModel has different implementations, each with their own View (= custom user controls).
Every implementation of IPartViewModel has a ZIndex property, ready to be bound.
All other bindings (labels, the tapped event, ...) work perfectly, but I cannot figure out what the binding should look like to control the ZIndex.
Many other questions on SO deal with this issue, but none for WinRT.
I ended up achieving this with a custom ItemsControl and overriding the method GetContainerForItemOverride where I return a ContentPresenter with a binding added to the ZIndex property
protected override DependencyObject GetContainerForItemOverride()
{
var cp = new ContentPresenter();
cp.SetBinding(Canvas.ZIndexProperty, new Binding { Path = new PropertyPath("ZIndex") });
return cp;
}