I'm trying to make a grid based on a UniformGrid to show the coordinates of each cell, and I want to show the values on the X and Y axes like so:
_A_ _B_ _C_ _D_
1 |___|___|___|___|
2 |___|___|___|___|
3 |___|___|___|___|
4 |___|___|___|___|
Anyway, in order to do that I need to know the number of columns and rows in the Uniform grid, and I tried overriding the 3 most basic methods where the arrangement / drawing happens, but the columns and rows in there are 0, even though I have some controls in my grid. What method can I override so my Cartesian grid knows how many columns and rows it has?
C#:
public class CartesianGrid : UniformGrid
{
protected override Size MeasureOverride(Size constraint)
{
Size size = base.MeasureOverride(constraint);
int computedColumns = this.Columns; // always 0
int computedRows = this.Rows; // always 0
return size;
}
protected override Size ArrangeOverride(Size arrangeSize)
{
Size size = base.ArrangeOverride(arrangeSize);
int computedColumns = this.Columns; // always 0
int computedRows = this.Rows; // always 0
return size;
}
protected override void OnRender(DrawingContext dc)
{`enter code here`
int computedColumns = this.Columns; // always 0
int computedRows = this.Rows; // always 0
base.OnRender(dc);
}
}
XAML:
<local:CartesianGrid>
<Label Content="Hello" />
<Label Content="Hello" />
<Label Content="Hello" />
<Label Content="Hello" />
<Label Content="Hello" />
<Label Content="Hello" />
</local:CartesianGrid>
Any help is greatly appreciated. Thanks!
There is no property exposed that gives you that information. If you have reflector you can see the calculation they do in the UpdateComputedValues method to find the number of rows and columns they use.
In the case where Rows, Columns, and FirstColumn are zero you can use something like this
int numVisibleChildren = this.Children.Count((c) => c.Visibility != Visibility.Collapsed);
int numColumns = (int)Math.Ceiling(Math.Sqrt(numVisibleChildren));
int numRows = (int)Math.Floor(Math.Sqrt(numVisibleChildren));
I haven't actually run the code though so there are probably some typos.
Here's the complete solution, handles specifying or not specifying the Columns and Rows of the UniformGrid:
public class CartesianGrid : UniformGrid
{
private int _columns;
private int _rows;
private int _margin = 20;
public CartesianGrid()
{
// add some margin so the letters and numbers do show up
this.Margin = new Thickness(_margin, _margin, 0, 0);
}
protected override void OnRender(DrawingContext dc)
{
double xOffset = (this.RenderSize.Width / _columns);
double yOffset = (this.RenderSize.Height / _rows);
double xCenterOffset = xOffset / 2;
double yCenterOffset = yOffset / 2.3;
for (int i = 0; i < _columns; i++)
{
dc.DrawText(
new FormattedText((i + 1).ToString(),
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Arial"),
20,
Brushes.Black), new Point((i * xOffset) + xCenterOffset, _margin * -1));
}
for (int i = 0; i < _rows; i++)
{
dc.DrawText(
new FormattedText(((char)(i + 65)).ToString(),
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Arial"),
20,
Brushes.Black), new Point(_margin * -1, (i * yOffset) + yCenterOffset));
}
base.OnRender(dc);
}
protected override Size ArrangeOverride(Size arrangeSize)
{
if (this.Columns != 0 && this.Rows != 0)
{
_rows = this.Rows;
_columns = this.Columns;
return base.ArrangeOverride(arrangeSize);
}
else
{
Size arrangedSize = base.ArrangeOverride(arrangeSize);
double maxChildDesiredWidth = 0.0;
double maxChildDesiredHeight = 0.0;
// Measure each child, keeping track of max desired width & height.
for (int i = 0, count = Children.Count; i < count; ++i)
{
UIElement child = Children[i];
Size childDesiredSize = child.DesiredSize;
if (maxChildDesiredWidth < childDesiredSize.Width)
{
maxChildDesiredWidth = childDesiredSize.Width;
}
if (maxChildDesiredHeight < childDesiredSize.Height)
{
maxChildDesiredHeight = childDesiredSize.Height;
}
}
if (maxChildDesiredHeight == 0 || maxChildDesiredWidth == 0)
return arrangedSize;
_columns = Convert.ToInt32(Math.Floor(this.DesiredSize.Width / maxChildDesiredWidth));
_rows = Convert.ToInt32(Math.Floor(this.DesiredSize.Height / maxChildDesiredHeight));
return arrangedSize;
}
}
}
Btw, this is what I wanted to achieve:
http://wpfdude.blogspot.com/2010/06/cartesian-grid.html
Thanks!
Related
I have two custom controls for displaying collections of comments and signatures. Each of these controls has a button for changing display style - show entire collection or just first 4 elements.
When I put these controls in a default WrapPanel - they work fine: expand or collapse depending on user's actions.
In my own custom WrapPanel with new ArrangeOverride function they also work fine.
Now I need WrapPanel with option "LastChildFill" (I found example here) and with custom "WrapDirection" (similar to FlowDirection). First or last element in WrapPanel have to fill all remaining space while it's more than MinWidth of the element.
So I've created third WrapPanel with custom MeasureOverride and ArrangeOverride. And it works as I want, but when I change control display style a collection inside a control changes, but MeasureOverride in my WrapPanel doesn't start until I change window size.
Update: MeasureOverride doesn't start for expanding first or last element which fills remaining space. Collapsing for this element works fine.
I don't understand what thing I have broken with custom MeasureOverride?
Update 2:
I fugured that my problem is similar to this: MeasureOverride not always called on children's property changes.
And attach MeasureOverride code.
public class WrapPanelFlowDirection : WrapPanel
{
public bool LastChildFill
{
get { return (bool)GetValue(LastChildFillProperty); }
set { SetValue(LastChildFillProperty, value); }
}
public static readonly DependencyProperty LastChildFillProperty =
DependencyProperty.Register("LastChildFill", typeof(bool), typeof(WrapPanelFlowDirection), new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsMeasure));
public WrapDirection WrapDirection
{
get { return (WrapDirection)GetValue(WrapDirectionProperty); }
set { SetValue(WrapDirectionProperty, value); }
}
public static readonly DependencyProperty WrapDirectionProperty =
DependencyProperty.Register("WrapDirection", typeof(WrapDirection), typeof(WrapPanelFlowDirection), new FrameworkPropertyMetadata(WrapDirection.LeftToRight, FrameworkPropertyMetadataOptions.AffectsMeasure));
#region Measure Override
protected override Size MeasureOverride(Size availableSize)
{
var panelSize = new Size(availableSize.Width, 0);
double minWidth = 0;
foreach (FrameworkElement child in this.InternalChildren)
{
child.Measure(availableSize);
minWidth += child.DesiredSize.Width;
}
int firstChildInLine = 0;
double currLineHeight = 0;
double currLineWidth = 0;
UIElementCollection children = this.InternalChildren;
for (int i = 0; i < children.Count; i++)
{
FrameworkElement child = children[i] as FrameworkElement;
if (child == null)
continue;
double w = 0;
if (child.MinWidth == 0)
w = child.DesiredSize.Width;
else
w = child.MinWidth;
if (currLineWidth + w <= availableSize.Width)
{
currLineWidth += w;
currLineHeight = Math.Max(currLineHeight, child.DesiredSize.Height);
}
else
{
MeasureOneLine(firstChildInLine, i - 1, ref currLineHeight, availableSize.Width);
panelSize.Height += currLineHeight;
currLineWidth = w;
currLineHeight = child.DesiredSize.Height;
if (w > availableSize.Width)
{
MeasureOneLine(i, i, ref currLineHeight, availableSize.Width);
panelSize.Height += currLineHeight;
currLineHeight = currLineWidth = 0;
firstChildInLine = i + 1;
}
else
{
firstChildInLine = i;
}
}
}
if (firstChildInLine < children.Count)
{
MeasureOneLine(firstChildInLine, children.Count - 1, ref currLineHeight, availableSize.Width);
panelSize.Height += currLineHeight;
}
if (double.IsInfinity(panelSize.Width))
panelSize.Width = minWidth;
return panelSize;
}
private void MeasureOneLine(int start, int end, ref double height, double totalWidth)
{
UIElementCollection children = this.InternalChildren;
if (WrapDirection == WrapDirection.LeftToRight)
{
for (int i = start; i < end; i++)
{
FrameworkElement child = children[i] as FrameworkElement;
if (child == null)
continue;
double w = 0;
if (child.MinWidth == 0)
w = child.DesiredSize.Width;
else
w = child.MinWidth;
if (i < end)
{
child.Measure(new Size(w, height));
totalWidth -= w;
}
else
{
child.Measure(new Size(totalWidth, double.PositiveInfinity));
height = Math.Max(height, child.DesiredSize.Height);
}
}
}
else
{
for (int i = end; i >= start; i--)
{
FrameworkElement child = children[i] as FrameworkElement;
if (child == null)
continue;
double w = 0;
if (child.MinWidth == 0)
w = child.DesiredSize.Width;
else
w = child.MinWidth;
if (i > start)
{
child.Measure(new Size(w, height));
totalWidth -= w;
}
else
{
child.Measure(new Size(totalWidth, double.PositiveInfinity));
height = Math.Max(height, child.DesiredSize.Height);
}
}
}
}
#endregion
I found solution here : How does a container know when a child has called InvalidateArrange?
And it worked for me.
public static void OnPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
UIElement panel= VisualTreeHelper.GetParent(source) as UIElement;
if(panel != null)
{
panel.InvalidateMeasure();
panel.InvalidateArrange();
}
}
I'm doing a custom panel, based(heriting) the WrapPanel.
I'm doing this because some UserControls I'm using in the WrapPanel don't wrap because they are able to use an "infinite" width, so a WrapPanel with a vertical orientation will never wrap.
I wanted to do one WrapPanel that ignore the width of the elements when having orientation = Vertical, and ignore the height of the elements when having an orientation = Horizontal.
Currently I've the following code:
protected override Size ArrangeOverride(Size finalSize)
{
int numberOfColumns, numberOfRows;
UIElement uiElement = InternalChildren.OfType<UIElement>().First();
if (Orientation == Orientation.Horizontal)
{
double desiredWidth = uiElement.DesiredSize.Width;
numberOfColumns = (int)Math.Round(finalSize.Width / desiredWidth);
numberOfRows = (int)Math.Ceiling(InternalChildren.Count / (double)numberOfColumns);
}
else
{
double desiredHeight = uiElement.DesiredSize.Height;
numberOfRows = (int)Math.Round(finalSize.Height / desiredHeight);
numberOfColumns = (int)Math.Ceiling(InternalChildren.Count / (double)numberOfRows);
}
int row = 0, column = 0;
Size elementSize = new Size(Math.Floor(finalSize.Width / numberOfColumns), Math.Floor(finalSize.Height / numberOfRows));
foreach (UIElement child in InternalChildren)
{
child.Arrange(new Rect(new Point(row * elementSize.Width, column * elementSize.Height), elementSize));
if (Orientation == Orientation.Horizontal)
{
column++;
if (column >= numberOfColumns)
{
column = 0;
row++;
}
}
else
{
row++;
if (row >= numberOfRows)
{
row = 0;
column++;
}
}
}
return finalSize;
}
The issue is that the finalSize that I receives is bigger than the "ActualSize" of the component(Width 2x the actualSize in fact).
What did I miss?
I am trying to create a Paginator that will in turn create an XpsDocument that I can preview with the and then print. I have the following code that I found on the web but do not understand how it is working.
The issue I am having is that if I run this as is, the pages are generated successfully. If I comment out the lines within OnRender() that generate the actual values of data (all the lines after Random...) I get pages that are about one row high and no text on them but they appear to be the correct length. What is it that is keeping the values of "Row Number" & "Column i" from being shown?
I have included 2 screen shots to illustrate.
public class TaxCodePrintPaginator : DocumentPaginator
{
private int _RowsPerPage;
private Size _PageSize;
private int _Rows;
private List<TaxCode> _dataList;
public TaxCodePrintPaginator(List<TaxCode> dt, int rows, Size pageSize)
{
_dataList = dt;
_Rows = rows;
PageSize = pageSize;
}
public override DocumentPage GetPage(int pageNumber)
{
int currentRow = _RowsPerPage * pageNumber;
var page = new PageElement(currentRow, Math.Min(_RowsPerPage, _Rows - currentRow))
{
Width = PageSize.Width,
Height = PageSize.Height
};
page.Arrange(new Rect(new Point(0, 0), PageSize));
return new DocumentPage(page);
}
public override bool IsPageCountValid
{ get { return true; } }
public override int PageCount
{ get { return (int)Math.Ceiling(_Rows / (double)_RowsPerPage); } }
public override Size PageSize
{
get { return _PageSize; }
set
{
_PageSize = value;
_RowsPerPage = 40;
//Can't print anything if you can't fit a row on a page
Debug.Assert(_RowsPerPage > 0);
}
}
public override IDocumentPaginatorSource Source
{ get { return null; } }
}
public class PageElement : UserControl
{
private const int PageMargin = 75;
private const int HeaderHeight = 25;
private const int LineHeight = 20;
private const int ColumnWidth = 140;
private int _CurrentRow;
private int _Rows;
public PageElement(int currentRow, int rows)
{
Margin = new Thickness(PageMargin);
_CurrentRow = currentRow;
_Rows = rows;
}
private static FormattedText MakeText(string text)
{
return new FormattedText(text, CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Tahoma"), 14, Brushes.Black);
}
public static int RowsPerPage(double height)
{
return (int)Math.Floor((height - (2 * PageMargin)
- HeaderHeight) / LineHeight);
}
protected override void OnRender(DrawingContext dc)
{
Point curPoint = new Point(0, 0);
dc.DrawText(MakeText("Row Number"), curPoint);
curPoint.X += ColumnWidth;
for (int i = 1; i < 4; i++)
{
dc.DrawText(MakeText("Column " + i), curPoint);
curPoint.X += ColumnWidth;
}
curPoint.X = 0;
curPoint.Y += LineHeight;
dc.DrawRectangle(Brushes.Black, null, new Rect(curPoint, new Size(Width, 2)));
curPoint.Y += HeaderHeight - LineHeight;
Random numberGen = new Random();
for (int i = _CurrentRow; i < _CurrentRow + _Rows; i++)
{
dc.DrawText(MakeText(i.ToString()), curPoint);
curPoint.X += ColumnWidth;
for (int j = 1; j < 4; j++)
{
dc.DrawText(MakeText(numberGen.Next().ToString()), curPoint);
curPoint.X += ColumnWidth;
}
curPoint.Y += LineHeight;
curPoint.X = 0;
}
}
}
Before
After
I want to be able to fill a rectangle with an animation on leftmousebuttondown (this will later be changed to run on load).
My rectangles are drawn to a canvas in code behind based on the data that is passed (one rectangle per row of data)
At the moment they are filled with a static image but I want this Fill to be an animation, a spinner if I can.
I am very new to Silverlight and am not sure how to achieve this. Can someone point me in the right direction?
My code (part) so far.
XAML:
<Canvas x:Name="Grid" Background="LightGray"></Canvas>
CS:
public partial class ProductView : UserControl
{
Processing processingDialog = new Processing();
private int colsRequired = 0;
private int rowsRequired = 0;
private const int minSize = 5;
private int cellSize = 1;
public ProductView()
{
InitializeComponent();
}
public void UpdateGrid(ObservableCollection<Product> productList)
{
calculateRowsCols(productList);
drawGrid(productList);
}
public void calculateRowsCols(ObservableCollection<Product> productList)
{
int tileCount = productList.Count();
double tileHeight = Grid.ActualHeight;
double tileWidth = Grid.ActualWidth;
if (tileCount == 0)
return;
double maxSize = Math.Sqrt((tileHeight * tileWidth) / tileCount);
double noOfTilesHeight = Math.Floor(tileHeight / maxSize);
double noOfTilesWidth = Math.Floor(tileWidth / maxSize);
double total = noOfTilesHeight * noOfTilesWidth;
cellSize = (maxSize < minSize) ? minSize : Convert.ToInt32(maxSize);
while ((cellSize >= minSize) && (total < tileCount))
{
cellSize--;
noOfTilesHeight = Math.Floor(tileHeight / cellSize);
noOfTilesWidth = Math.Floor(tileWidth / cellSize);
total = noOfTilesHeight * noOfTilesWidth;
}
rowsRequired = Convert.ToInt32(Math.Floor(tileHeight / cellSize));
colsRequired = Convert.ToInt32(Math.Floor(tileWidth / cellSize));
}
private void drawCell(int row, int col, string label, Color fill)
{
Rectangle innertec = new Rectangle();
innertec.Height = cellSize * 0.7;
innertec.Width = cellSize * 0.9;
innertec.StrokeThickness = 1;
innertec.Stroke = new SolidColorBrush(Colors.Black);
ImageBrush imageBrush = new ImageBrush();
imageBrush.ImageSource = new BitmapImage(new Uri("Assets/loading.png", UriKind.Relative));
innertec.Fill = imageBrush;
Grid.Children.Add(innertec);
Canvas.SetLeft(innertec, (col * cellSize) + ((cellSize - innertec.Width) / 2));
Canvas.SetTop(innertec, row * cellSize + 4);
Border productLabelBorder = new Border();
Grid.Children.Add(productLabelBorder);
Canvas.SetLeft(productLabelBorder, col * cellSize);
Canvas.SetTop(productLabelBorder, row * cellSize);
TextBlock productLabel = new TextBlock();
productLabel.Margin = new Thickness(0, innertec.Height + 5, 0, 5);
productLabel.TextAlignment = TextAlignment.Center;
productLabel.TextWrapping = TextWrapping.NoWrap;
productLabel.TextTrimming = TextTrimming.WordEllipsis;
productLabel.MaxWidth = cellSize;
productLabel.Height = cellSize * 0.3;
productLabel.Width = cellSize;
productLabel.Text = label;
productLabel.HorizontalAlignment = HorizontalAlignment.Center;
productLabel.VerticalAlignment = VerticalAlignment.Center;
productLabel.FontSize = cellSize * 0.13;
ToolTipService.SetToolTip(productLabel, label);
productLabelBorder.Child = productLabel;
}
public void drawGrid(ObservableCollection<Product> data)
{
int dataIndex = 0;
Grid.Children.Clear();
for (int i = 0; i < rowsRequired; i++)
{
for (int j = 0; j < colsRequired; j++)
{
Product product = (dataIndex < data.Count) ? data.ElementAt(dataIndex) : null;
if (product != null)
{
drawCell(i, j, product.productName, Colors.White);
}
dataIndex++;
}
}
}
}
Any help anyone can give, even a pointer in the right direction would be great.
Thanks in advance
Try creating custom control which will encapsulate everything you want from rectangle to do.
you can add new VisualState "MouseDownState" and do required animatin in xaml.
Please let me know if you need more details regarding the implementation.
late simply add new control instead of rectangle.
i first sorry about my english,i´ll try to explain want i want to do
i need to draw an ellipse with wpf that represents an aura and it´s "deformations" representing problematic zones in it,in short an ellipse that can be deformed in running time in specific points
I'm trying to draw several bezier curves forming an ellipse but it´t very difficult (and i don´t know how) to make points that can be dragged forming convex or hollow zones in that ellipse.
¿i made myselft clear in my spanglish? ¿is there an easy way to do that?
Thanks in advance
I don't know what exactly you are trying to do but I recommend making a high resolution version of the ellipse and keeping track of the deformations yourself. Here is a sample program to get you started.
For this demo the XAML is simple:
<Canvas Name="canvas" Focusable="True" KeyDown="canvas_KeyDown" MouseDown="canvas_MouseDown" MouseMove="canvas_MouseMove" MouseUp="canvas_MouseUp"/>
and a the code-behind:
public partial class EllipseDemo : Window
{
const int resolution = 1000;
const double major = 150;
const double minor = 100;
const double xOrigin = 200;
const double yOrigin = 200;
const double radius = 10;
const double scale = 0.1;
const double spread = 10;
const double magnitude = 10;
Path path;
Ellipse controlPoint;
LineSegment[] segments;
double[] deformation;
double[] perturbation;
int controlPointIndex;
public EllipseDemo()
{
InitializeComponent();
segments = new LineSegment[resolution];
deformation = new double[resolution];
perturbation = new double[resolution];
for (int i = 0; i < resolution; i++)
{
var x = i >= resolution / 2 ? i - resolution : i;
perturbation[i] = magnitude * Math.Exp(-Math.Pow(scale * x, 2) / spread);
}
path = new Path();
path.Stroke = new SolidColorBrush(Colors.Black);
path.StrokeThickness = 5;
CalculateEllipse();
canvas.Children.Add(path);
controlPoint = new Ellipse();
controlPoint.Stroke = new SolidColorBrush(Colors.Red);
controlPoint.Fill = new SolidColorBrush(Colors.Transparent);
controlPoint.Width = 2 * radius;
controlPoint.Height = 2 * radius;
MoveControlPoint(0);
canvas.Children.Add(controlPoint);
canvas.Focus();
}
void CalculateEllipse()
{
for (int i = 0; i < resolution; i++)
{
double angle = 2 * Math.PI * i / resolution;
double x = xOrigin + Math.Cos(angle) * (major + deformation[i]);
double y = yOrigin + Math.Sin(angle) * (minor + deformation[i]);
segments[i] = new LineSegment(new Point(x, y), true);
}
var figure = new PathFigure(segments[0].Point, segments, true);
var figures = new PathFigureCollection();
figures.Add(figure);
var geometry = new PathGeometry();
geometry.Figures = figures;
path.Data = geometry;
}
void MoveControlPoint(int index)
{
controlPointIndex = index;
Canvas.SetLeft(controlPoint, segments[index].Point.X - radius);
Canvas.SetTop(controlPoint, segments[index].Point.Y - radius);
}
bool mouseDown;
void canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
if (Mouse.DirectlyOver != controlPoint)
return;
mouseDown = true;
controlPoint.CaptureMouse();
}
void canvas_MouseMove(object sender, MouseEventArgs e)
{
if (!mouseDown)
return;
int index = FindNearestIndex(e.GetPosition(canvas));
MoveControlPoint(index);
}
void canvas_MouseUp(object sender, MouseButtonEventArgs e)
{
if (!mouseDown)
return;
controlPoint.ReleaseMouseCapture();
mouseDown = false;
}
private void canvas_KeyDown(object sender, KeyEventArgs e)
{
int delta = 0;
switch (e.Key)
{
case Key.Up:
delta = 1;
break;
case Key.Down:
delta = -1;
break;
}
if (delta == 0)
return;
int index = controlPointIndex;
for (int i = 0; i < resolution; i++)
deformation[(i + index) % resolution] += delta * perturbation[i];
CalculateEllipse();
MoveControlPoint(index);
}
int FindNearestIndex(Point point)
{
var min = double.PositiveInfinity;
var index = -1;
for (int i = 0; i < segments.Length; i++)
{
var vector = point - segments[i].Point;
var distance = vector.LengthSquared;
if (distance < min)
{
index = i;
min = distance;
}
}
return index;
}
}
This works mostly with a Path represented by line segments and an Ellipse as a control point. The mouse can move the control point around the ellipse and then the arrow keys add or remove a canned perturbation. Everything is hard coded but if you are OK with the math then it should help you get started.
Here's the program in action: