Emulates Windows 8 Start Menu Tile Layout Engine - c#

So anyone out there knows of sample code or control that perfectly emulates the Windows 8 Start Menu Tile Layout Engine?
It should support mixed Square and Rectangle Tiles, and repacks the square tiles over or under rectangle tiles properly.
Note: WrapPanel works if ALL TILES are Square. But once you mix in tiles that span 2-Squares worth of space, the layout breaks, and is inconsistent with the Windows 8 Start Menu
I am expecting code that extends the WPF Panel.
Disclaimer: Yes I have searched the Internet, the closest thing I've found is the CodeProject example, but that only works if all tiles are same-sized squares.

I've looked around myself and couldn't find anything to do what I/we want. I knew that to get this behavior we'd need some sort of custom panel object, so I set about creating one...
What it boils down to, is the tiles need to be arranged vertically, with double-width tiles taking up a whole row in that column, and normal width tiles to pair up. When it reaches the bottom of the container, it needs to create a new column and follow the same pattern.
Here's my implementation:
public class MetroTilePanel : Panel
{
protected override Size ArrangeOverride(System.Windows.Size finalSize)
{
double x = 0, y = 0, colWidth = 0, rowHeight = 0;
int col = 0;
colWidth = Children.Cast<UIElement>().Select(c => c.DesiredSize.Width).Max();
foreach (UIElement child in Children)
{
rowHeight = Math.Max(rowHeight, child.DesiredSize.Height);
if (x + child.DesiredSize.Width > (colWidth * (col + 1)))
{
// New row
y += rowHeight;
x = (colWidth * (col));
rowHeight = child.DesiredSize.Height;
}
if (y + rowHeight > finalSize.Height)
{
// New column
col++;
x = (colWidth * (col));
y = 0;
}
child.Arrange(new Rect(x, y, child.DesiredSize.Width, child.DesiredSize.Height));
x += child.DesiredSize.Width;
}
return finalSize;
}
protected override Size MeasureOverride(Size availableSize)
{
double x = 0, y = 0, colWidth = 0;
foreach (UIElement child in Children)
{
child.Measure(availableSize);
if (x + child.DesiredSize.Height > availableSize.Height)
{
x += colWidth;
y = 0;
colWidth = 0;
}
y += child.DesiredSize.Height;
if (child.DesiredSize.Width > colWidth)
{
colWidth = child.DesiredSize.Width;
}
}
x += colWidth;
var resultSize = new Size();
resultSize.Width = double.IsPositiveInfinity(availableSize.Width) ? x : availableSize.Width;
resultSize.Height = double.IsPositiveInfinity(availableSize.Height) ? y : availableSize.Height;
return resultSize;
}
}
Screenshot of the control in action:
Disclaimers:
The MeasureOverride only works by chance, and isn't setup correctly.
If you want the nice MetroTile layout, then stick to uniform sizes i.e 100x100 and 200x100
I haven't fully tested it, but I will be implementing it into my fake-Metro app, so if you want to see any future changes, just holler.
If you want the proper GridView tiling behavior, then we'd have to create a brand new control (to support dragging items around etc).
I hope this helps.

These are the two different libraries I evaluated for my project to create a Windows 8 like startpage in WPF:
Telerik RadTileList (paid)
mahapps.metro (open source)

Related

Custom virtualizing panel freezes UI thread when user scrolls

I found a VirtualizingWrapPanel(VWP) project here which, I thought, could help me with optimizing my ListView scrolling performance. The listView have to has four columns and multiple rows to display the source items. So I tried to use this VWP, but scrolling(I made it as DoubleAnimation) smoothness still sucks, the framerate drops down to about 30 fps and it's very noticeable. I copied the source code from the link above to try to debug it and understand what is the reason of such a bad performance.
After several hours a found out that ArrangeOverride method of VWP and MeasureOverride method of its "inheritance parent" can execute about 20-30ms (1000ms / 30ms = ~30fps) and that's it, I found the reason why it's so slow... but why?
MeasureOverride method calls two potentially heavy methods: RealizeItems and VirtualizeItems
protected virtual void RealizeItems()
{
var startPosition = ItemContainerGenerator.GeneratorPositionFromIndex(ItemRange.StartIndex);
int childIndex = startPosition.Offset == 0 ? startPosition.Index : startPosition.Index + 1;
using (ItemContainerGenerator.StartAt(startPosition, GeneratorDirection.Forward, true))
{
for (int i = ItemRange.StartIndex; i <= ItemRange.EndIndex; i++, childIndex++)
{
UIElement child = (UIElement)ItemContainerGenerator.GenerateNext(out bool isNewlyRealized);
if (isNewlyRealized || /*recycled*/!InternalChildren.Contains(child))
{
if (childIndex >= InternalChildren.Count)
{
AddInternalChild(child);
}
else
{
InsertInternalChild(childIndex, child);
}
ItemContainerGenerator.PrepareItemContainer(child);
child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
}
if (child is IHierarchicalVirtualizationAndScrollInfo groupItem)
{
groupItem.Constraints = new HierarchicalVirtualizationConstraints(
new VirtualizationCacheLength(0),
VirtualizationCacheLengthUnit.Item,
new Rect(0, 0, ViewportWidth, ViewportHeight));
child.Measure(new Size(ViewportWidth, ViewportHeight));
}
}
}
}
protected virtual void VirtualizeItems()
{
for (int childIndex = InternalChildren.Count - 1; childIndex >= 0; childIndex--)
{
var generatorPosition = GetGeneratorPositionFromChildIndex(childIndex);
int itemIndex = ItemContainerGenerator.IndexFromGeneratorPosition(generatorPosition);
if (!ItemRange.Contains(itemIndex))
{
if (VirtualizationMode == VirtualizationMode.Recycling)
{
ItemContainerGenerator.Recycle(generatorPosition, 1);
}
else
{
ItemContainerGenerator.Remove(generatorPosition, 1);
}
RemoveInternalChildRange(childIndex, 1);
}
}
}
So I understand that developers could make a mistake in code, I also understand that there may not be a single thing that can be optimized, but I really cannot understand why it is so slow.. The loops iterates throught about 40-50 UIElement objects, every of which has two child objects(TextBlock and ImageSource) and that's not such a huge number of objects..
I want to use parallel tasks in these loops, but UIElement and others is not thread-safe and can only be accessed in UI Thread... or can?
How can I increase performance of these methods? Can there be implemented efficient multithreading?
Thanks!
I found this method, which calls InvalidateMeasure() every time vertical scroll offset changes. Because of DoubleAnimation I use to animate vertical scroll offset this is code really often called, but if there is no one thing to change measure and arrange methods executes for about 1-2ms in total, else - a lot slowlier.
public void SetVerticalOffset(double offset)
{
if (offset < 0 || Viewport.Height >= Extent.Height)
{
offset = 0;
}
else if (offset + Viewport.Height >= Extent.Height)
{
offset = Extent.Height - Viewport.Height;
}
Offset = new Point(Offset.X, offset);
ScrollOwner?.InvalidateScrollInfo();
InvalidateMeasure();
}
UPDATE: I had opened Profiler and found out, that a lot of time(7-10 and more ms) is taken by layout operations.
UPDATE 2: The code bellow offsets(arranges) all the visible items
protected override Size ArrangeOverride(Size finalSize)
{
double offsetX = GetX(Offset);
double offsetY = GetY(Offset);
/* When the items owner is a group item offset is handled by the parent panel. */
if (ItemsOwner is IHierarchicalVirtualizationAndScrollInfo groupItem)
{
offsetY = 0;
}
Size childSize = CalculateChildArrangeSize(finalSize);
CalculateSpacing(finalSize, out double innerSpacing, out double outerSpacing);
for (int childIndex = 0; childIndex < InternalChildren.Count; childIndex++)
{
UIElement child = InternalChildren[childIndex];
int itemIndex = GetItemIndexFromChildIndex(childIndex);
int columnIndex = itemIndex % itemsPerRowCount;
int rowIndex = itemIndex / itemsPerRowCount;
double x = outerSpacing + columnIndex * (GetWidth(childSize) + innerSpacing);
double y = rowIndex * GetHeight(childSize);
if (GetHeight(finalSize) == 0.0)
{
/* When the parent panel is grouping and a cached group item is not
* in the viewport it has no valid arrangement. That means that the
* height/width is 0. Therefore the items should not be visible so
* that they are not falsely displayed. */
child.Arrange(new Rect(0, 0, 0, 0));
}
else
{
child.Arrange(CreateRect(x - offsetX, y - offsetY, childSize.Width, childSize.Height));
}
}
return finalSize;
}

"Parameter must be positive and < Height. Parameter name: y" error

I am developing a component for Grasshopper 3D, which is a Rhino (architecture) plugin.
Using the Render() method, this draws a heatmap image onto the canvas. Isolating my other methods and constructors, I highly believe this method is causing my issue.
protected override void Render(Grasshopper.GUI.Canvas.GH_Canvas canvas, Graphics graphics, Grasshopper.GUI.Canvas.GH_CanvasChannel channel) {
// Render the default component.
base.Render(canvas, graphics, channel);
// Now render our bitmap if it exists.
if (channel == Grasshopper.GUI.Canvas.GH_CanvasChannel.Wires) {
KT_HeatmapComponent comp = Owner as KT_HeatmapComponent;
if (comp == null)
return;
List<HeatMap> maps = comp.CachedHeatmaps;
if (maps == null)
return;
if (maps.Count == 0)
return;
int x = Convert.ToInt32(Bounds.X + Bounds.Width / 2);
int y = Convert.ToInt32(Bounds.Bottom + 10);
for (int i = 0; i < maps.Count; i++) {
Bitmap image = maps[i].Image;
if (image == null)
continue;
Rectangle mapBounds = new Rectangle(x, y, maps[i].Width * 10, maps[i].Height * 10);
mapBounds.X -= mapBounds.Width / 2;
Rectangle edgeBounds = mapBounds;
edgeBounds.Inflate(4, 4);
GH_Capsule capsule = GH_Capsule.CreateCapsule(edgeBounds, GH_Palette.Normal);
capsule.Render(graphics, Selected, false, false);
capsule.Dispose();
// Unnecessary graphics.xxxxxx methods and parameters
y = edgeBounds.Bottom + 10;
}
}
}
The error I receive when I attempt to render things onto my canvas is:
1. Solution exception:Parameter must be positive and < Height.
Parameter name: y
From my research, it appears to happen the most when you encounter array overflows.
My research links:
http://www.codeproject.com/Questions/158055/Help-in-subtraction-of-two-images
Exception on traveling through pixels BMP C#
http://www.c-sharpcorner.com/Forums/Thread/64792/
However, the above examples apply mostly to multi-dimensional arrays, while I have a one-dimensional.
I was wondering if anyone else has had this issue before, and could give me some pointers and guidance?
Thanks.
int x = Convert.ToInt32(Bounds.X + Bounds.Width / 2);
int y = Convert.ToInt32(Bounds.Bottom + 10);
Your error is telling you that y must be less than the height, but you are setting it to 10 more than your height because you are adding to the Bounds.Bottom.
maps[i].Height * 10
You also need to make sure that your calculated Height is what you think it is and compare that y is valid against it.

static distance between the panels

I have a dynamically created N panels, the distance between them in height 15px
panel.Location = new Point (x, y);
y = panel.Bottom + 15;
I can make the width of the smaller, and so I need distance in height between the panels was always 15px
I have a method with different checks for resize, and I try changes distance, but it always works differently...
public void checkResize(string msg_out, object panel_sender, object text_msg_sender, int panHei, int numbs)
{
Panel pan_item = (Panel)panel_sender;
Label lab_item = (Label)text_msg_sender;
char[] msg_arr = msg_out.ToCharArray();
int panWidRaz = 308 - pan_item.Width;
int panWidw = pan_item.Width;
if (int.Parse(pan_item.Name) != numbs - 1)
{
if (panWidw < buff)
{
/* if (panWidRaz % 15 == 0)
{
for (int i = int.Parse(pan_item.Name); i >= 0; i--)
{
panel1.Controls[i.ToString()].Location = new Point(panel1.Controls[i.ToString()].Location.X, panel1.Controls[i.ToString()].Location.Y + 1);
}
}*/
//width control becomes smaller panels are becoming more in height, it is necessary that the distance between the panels remained 15px
}
if (panWidw > buff)
{
/*if (panWidRaz % 15 == 0)
{
for (int i = int.Parse(pan_item.Name); i >= 0; i--)
{
panel1.Controls[i.ToString()].Location = new Point(panel1.Controls[i.ToString()].Location.X, panel1.Controls[i.ToString()].Location.Y - 1);
}
}*/
//width control becomes bigger panels are becoming less in height, it is necessary that the distance between the panels remained 15px
}
buffCountPan++;
if (buffCountPan == panel1.Controls.Count - 1)
{
buff = panWidw;
buffCountPan = 0;
}
if (msg_arr.Length > 26)
{
int panWid = (308 - pan_item.Width) / 5;
int panWidLab = 308 - pan_item.Width;
pan_item.Height = panHei + panWid;
lab_item.MaximumSize = new System.Drawing.Size(300 - panWidLab, 100);
lab_item.MinimumSize = new System.Drawing.Size(300 - panWidLab, 14);
}
}
}
I can't post image here... reputation... i make scrin of work my panel
http://pixs.ru/showimage/Bezimeni1p_9639414_8969341.png
If you want to make it simple, when you add those panel which i guess is done dynamically. You can set the panel.tag = "[Order]". so an order number assign so you know which one is first on top and the second and go on...
Const int MinimumStartLocation = 0;
Const int DistanceBetweenPanel = 15;
private void setPanelLocation(Panel pnl)
{
// retreive the order of this panel
int iPanelOrder = Convert.ToInt32(pnl.Tag);
// the first panel must show on top and have specific location
if(iPanelOrder == 0)
{
pnl.Top = MinimumStartLocation;
}
else
{
// set the top of the panel to the bottom value of the panel just before him in the order plus the constant
pnl.Top = this.Controls.OfType<Panel>().ToList().Find(pan => Convert.ToInt32(pan.Tag) == iPanelOrder -1).Bottom + DistanceBetweenPanel;
}
}
now use this LINQ command it will call the method above for each panel in the proper order.
this.Controls.OfType<Panel>().OrderBy(x => Convert.ToInt32(x.Tag)).ToList().ForEach(pan => setPanelLocation(pan));
here a sample project quickly done download here
Edit: updated link
It might be worth taking a look at the Flow Layout Panel. When you add a control to a flow layout panel it will automatically be positioned either to the left, right, top or bottom of any existing controls within that flow layout panel (depending on the layout direction property that you specify).
Similarly, if you remove or resize a control contained within a flow layout panel the positions of the controls will be automatically updated for you.
In your case it should be as simple as adding a new flow layout panel to your form then, for each panel that you add to the flow layout panel, setting the top margin to 15px and the bottom margin to 0px; the flow layout panel will take care of the rest. When panels are added, removed or resized the flow layout panel will ensure that they are still displayed correctly with a margin of 15px between them.

How to create an outline of an image if I know the background color?

I want to create a simple program that you open any given image and select 2 colors: BackgroundColor and OutlineColor. Then make an outline around the "object".
Here is my code so far:
for (int y = 1; y < height - 1; y++) //iterate trough every pixel
{ //in my bitmap
for (int x = 1; x < width - 1; x++)
{
//i want to put a pixel only if the the curent pixel is background
if (bitmap.GetPixel(x, y) != BackgroundColor)
continue;
var right = bitmap.GetPixel(x + 1, y);
var down = bitmap.GetPixel(x, y + 1);
var up = bitmap.GetPixel(x, y - 1);
var left = bitmap.GetPixel(x - 1, y);
//get the nearby pixels
var neibours = new List<Color> {up, down, left, right};
var nonBackgroundPix = 0;
//then count how many are not outline nor background color
foreach (Color neibour in neibours)
{
if (neibour != BackgroundColor && neibour != OutlineColor)
{
nonBackgroundPix++;
}
}
//finaly put an outline pixel only if there are 1,2 or 3 non bg pixels
if (nonBackgroundPix > 0 && nonBackgroundPix < 4)
{
bitmap.SetPixel(x, y, OutlineColor);
}
}
}
And here comes the problem when I run my code and input
I get
And what I want is
If you find a problem in my code, know better algorithm for doing this or manage to do it in some way just tell me. Thanks in advance guys!
The problem: You are changing the background color into a non-background color which is then being picked up by subsequent pixel checks.
The solution:
I'd suggest storing a new array with pixels "to be changed later" and then once the full image is mapped go back and set those. (Also, you can change it immediately and then add a flag to the pixel you can check for, but this is more logic you'd need to implement such as checking against a boolean array)

Specifying number of items per row using a WrapPanel

I'm attempting to create an application that looks much like the Windows 8 Metro UI with the tiles.. right now if you click on a tile I have an animation that increases the width and reveals more info on that tile.
I have the tiles laid out in a WrapPanel which is nice because if I resize the tile the other tiles next to it move and keep it's margin perfectly. However I've been researching this for awhile, I would like to if possible limit the number of items in the wrap panel to two wide (Right now it's three wide) as if you select one of the tiles it resizes itself and pushes a tile next to it (or if it's the end tile it will push itself) to the next row, while my animations are smooth it doesn't look the best presentation-wise..
Could someone point me towards how I might specify my wrappanel to only have a width of two items across?
Any help is very much appreciated.
Try making your own wrap panel deriving from standard wrap panel as described in this post in detail. Post addresses the same issue which you are trying to solve.
public class MyWrapPanel : WrapPanel
{
public int MaxRows
{
get { return (int)GetValue(MaxRowsProperty); }
set { SetValue(MaxRowsProperty, value); }
}
public static readonly DependencyProperty MaxRowsProperty =
DependencyProperty.Register("MaxRows", typeof(int), typeof(MyWrapPanel), new UIPropertyMetadata(4));
protected override Size ArrangeOverride(Size finalSize)
{
Point currentPosition = new Point();
double ItemMaxHeight = 0.0;
int RowIndex = 0;
foreach (UIElement child in Children)
{
ItemMaxHeight = ItemMaxHeight > child.DesiredSize.Height ? ItemMaxHeight : child.DesiredSize.Height;
if (currentPosition.X + child.DesiredSize.Width > this.DesiredSize.Width)
{
currentPosition = new Point(0, currentPosition.Y + ItemMaxHeight);
ItemMaxHeight = 0.0;
RowIndex++;
}
if (RowIndex < MaxRows)
{
child.Visibility = System.Windows.Visibility.Visible;
Rect childRect = new Rect(currentPosition, child.DesiredSize);
child.Arrange(childRect);
}
else
{
Rect childRect = new Rect(currentPosition, new Size(0,0));
child.Arrange(childRect);
}
currentPosition.Offset(child.DesiredSize.Width, 0);
}
return finalSize;
}
protected override Size MeasureOverride(Size availableSize)
{
return base.MeasureOverride(availableSize);
}
}

Categories

Resources