I'm porting a large iOS codebase to a Xamarin.Forms app. We have a lot of custom views which perform their layout logic by making calculations in -layoutSubviews. The codebase is too large for me to port in time if I need to reinterpret these calculations in terms of Stack or Grid layouts. What I really want is a direct equivalent, where I can add the equivalent subviews to our views without worrying about where they go, and then a method which is called when the view's bounds change inside which I can set the new bounds of the subviews. Then I can directly port our existing iOS code.
Is there some equivalent in Xamarin.Forms for -layoutSubviews?
You can create your own Layout by deriving from Xamarin.Forms.Layout class.
public class CustomLayout : Layout<View>
{
public CustomLayout ()
{
}
}
The layout must override the LayoutChildren method. This method is responsible for positioning children on screen.
Children can be measured by using the GetSizeRequest method, which will return both the desired size and the minimum size the child desires.
protected override void LayoutChildren (double x, double y, double width, double height)
{
for (int i = 0; i < Children.Count; i++) {
var child = (View) Children[i];
// skip invisible children
if(!child.IsVisible)
continue;
var childSizeRequest = child.GetSizeRequest (double.PositiveInfinity, height);
var childWidth = childSizeRequest.Request.Width;
LayoutChildIntoBoundingRegion (child, new Rectangle (x, y, childWidth, height));
x += childWidth;
}
}
This method will automatically be called whenever the layout needs to be recomputed. If your layout consists of hardcoded or fixed size elements, hard code their sizes into this algorithm instead of measuring. GetSizeRequest calls are some of the most expensive calls that can be made, and are not predictable in their runtime as the subtree may be arbitrary complex. Fixing their size is a great way to get a performance boost if dynamic sizing is not required.
Implementing OnSizeRequest is required to make sure the new layout is sized correctly when placed inside other layouts. During layout cycles this method may be called many times depending on the layout above it and how many layout exceptions are required to resolve the current layout hierarchy.
protected override SizeRequest OnSizeRequest (double widthConstraint, double heightConstraint)
{
var height = 0;
var minHeight = 0;
var width = 0;
var minWidth = 0;
for (int i = 0; i < Children.Count; i++) {
var child = (View) Children[i];
// skip invisible children
if(!child.IsVisible)
continue;
var childSizeRequest = child.GetSizeRequest (double.PositiveInfinity, height);
height = Math.Max (height, childSizeRequest.Minimum.Height);
minHeight = Math.Max (minHeight, childSizeRequest.Minimum.Height);
width += childSizeRequest.Request.Width;
minWidth += childSizeRequest.Minimum.Width;
}
return new SizeRequest (new Size (width, height), new Size (minWidth, minHeight));
}
You can read the whole tutorial of how to create a Custom layout here.
I am not sure if there is a an equivalent in forms for layoutSubviews but the calculations that you are talking about can be done inside a method called:
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
}
You need to inherit from a ContentPage or Any Page to override this method.
Related
I'm following a snake tutorial right now and I wrote the exact thing as said, but it won't even show the rectangles of the snake and food.enter code here
I'm using Windows Form Application.
I made separate classes - Food; Snake; And the one for the form.
//Snake class
public Rectangle[] Body;
private int x = 0, y = 0, width = 20, height = 20;
public Snake()
{
Body = new Rectangle[1];
Body[0] = new Rectangle(x, y, width, height);
}
public void Draw()
{
for (int i = Body.Length - 1; i < 0; i--)
Body[i] = Body[i - 1];
}
public void Draw (Graphics graphics)
{
graphics.FillRectangles(Brushes.AliceBlue, Body);
}
public void Move (int direction)
{
Draw();
//Food Class
public class Food
{
public Rectangle Piece;
private int x, y, width = 20, height = 20;
public Food(Random rand)
{
Generate(rand);
Piece = new Rectangle(x, y, width, height);
}
public void Draw(Graphics graphics)
{
Piece.X = x;
Piece.Y = y;
graphics.FillRectangle(Brushes.Red, Piece);
You are not inheriting your body and food classes from any form of drawable context objects. So the "draw" routine would need to be explicitly called any time a change happens. You appear to be trying to mimic the structure of a WinForms UI component, where it has its own built-in Draw() method that is implicitly called any time the UI needs to update.
Also, since you are calling "Draw" without any parameters, that should be throwing an error unless you have an overload somewhere that doesn't require parameters. In which case, there would be no graphics context to draw to.
I'm no expert in doing game graphics, but I do know that constantly calling the redraw method of a UI component is exceptionally inefficient. There are overrides for the Invalidate() method where you can provide rectangles to invalidate only small portions of the entire component. That can help with improving redraw rate.
I would suggest having a single renderable UI component on screen that links to your data objects. And override the draw() method of that component so that it draws the entire game board (or portions of it based on the invalidated regions), based on the data stored in your game objects.
I am an hobbyist .NET developer. I am developing a Windows Forms app and this picture shows want I want to use a chart control for:
Basically, I want to change the upper part of every column which is taller than a certain threshold (400 in the example above). I have searched around a bit, but no joy yet. Any ideas?
EDIT: I have a graceful workaround, but it's still frustrating not to have something available in the control itself. I have created two series, red and blue. When I add a point I check if the height is bigger than the threshold. If yes, I add the point to the red series, and I also add a point of threshold height to the blue series. If not, I add it to the blue series as is. The code can explain better than me:
private void AddPointToChart(Chart chart, int x, int y)
{
if (this.threshold < y)
{
chart.Series[1].Points.AddXY(x, y);
y = this.threshold;
}
chart.Series[0].Points.AddXY(x, y);
}
As per the original question, StackedColumn is the way to go!
And for your edit, on option (not an easy one) is to override virtual methods of Chart to add your own behaviour. I came up with an example after some experimentation:
public class ThresholdColumnChart : Chart
{
private double _threshold = 50d;
public double Threshold
{
get { return _threshold; }
set { _threshold = value; Invalidate(); }
}
public ThresholdColumnChart() : base() { }
protected override void OnCustomize()
{
base.OnCustomize();
if (Series.Count != 1)
return;
Series.Add(new Series());
foreach (var dataPoint in Series[0].Points)
{
var newDataPoint = new DataPoint();
newDataPoint.XValue = dataPoint.XValue;
newDataPoint.YValues[0] = (dataPoint.YValues[0] > _threshold ?
dataPoint.YValues[0] - _threshold : 0);
Series[1].Points.Add(newDataPoint);
if (dataPoint.YValues[0] > _threshold)
dataPoint.YValues[0] = _threshold;
}
Series[0].ChartType = SeriesChartType.StackedColumn;
Series[1].ChartType = SeriesChartType.StackedColumn;
Series[1].Color = Color.Red;
}
protected override void OnPostPaint(ChartPaintEventArgs e)
{
base.OnPostPaint(e);
if (!(e.ChartElement is ThresholdBarChart))
return;
if (Series.Count != 2)
return;
for (int i = 0; i < Series[0].Points.Count; i++)
Series[0].Points[i].YValues[0] += Series[1].Points[i].YValues[0];
Series.Remove(Series[1]);
}
}
In short the OnCustomize(...) method is called just before the chart is drawn (before OnPrePaint(...)) at which point it creates a second series to be used for drawing. The new series is constructed with the threshold in mind, and the first series is reduced not to exceed the threshold just like the example in your edit.
After the chart is done painting OnPostPaint(...) will be called and the first series will be restored to it's original values, and the "extra" series is removed.
Both OnPrePaint(...) and OnPostPaint are actually called multiple times when a chart is drawn, hence why I had to put if (!(e.ChartElement is ThresholdBarChart)) return; in; to make sure we only remove the series once the chart has finnished painting.
After adding the code and compiling once you will get a new usercontrol added to the design toolbox called "ThresholdColumnChart". You can set the threshold from the Property "Threshold" (also in design). I wouldnt trust this code with my life but it should serve as a starting point.
I'm programming a tetris clone for my C# school project. I am using Microsoft Visual Studio 2012. The game itself is implemented as a two dimensional array of blocks(List of Lists of blocks) and every block has its own texture (bmp image). I am drawing the whole array onto a PictureBox control and this is where the problem starts. When updating the image on the PictureBox (moving/rotating the active shape) the game slightly lags. I tried to draw on a Panel control instead but the result was the same. I have a rough idea what might cause the lag but I don't know exactly how to get rid of it.
This is the draw method of the game "grid":
public void Draw(Graphics g)
{
Brush brush;
Font font = new System.Drawing.Font( "Arial", 5);
for (int i = 0; i < Width; i++)
for (int j = 0; j < Height; j++)
{
brush = new TextureBrush(Blocks[i][j].Texture);
if (Blocks[i][j].Occupied==true)
g.FillRectangle(brush, i * 20, j * 20, i * 20 + Blocks[i][j].Texture.Width, j * 20 + Blocks[i][j].Texture.Height);
}
}
This is the draw method of the active tetromino:
public void Draw(Graphics g)
{
Brush brush = new TextureBrush(Blocks[0].Texture);
foreach (FullBlock b in Blocks)
g.FillRectangle(brush, b.x * 20, b.y * 20,b.Texture.Width, b.Texture.Height);
}
The game itself then use both of them (double buffering attempt):
public void GameDraw(PictureBox p)
{
Graphics g = Graphics.FromImage(gb);
gameGrid.Draw(g);
PlayingShape.Draw(g);
p.Image = gb;
p.Refresh();
}
where "gb" is a private Bitmap variable I create just once in the class constructor (to reduce (unsuccessfully) the lag).
The GameDraw method is called whenever the state of the game is changed (e.g. moving/rotating the active tetromino and every "gravity" tick)
You need Double buffering, which you did not set. Quoting MSDN:
Double buffering uses a memory buffer to address the flicker problems
associated with multiple paint operations. When double buffering is
enabled, all paint operations are first rendered to a memory buffer
instead of the drawing surface on the screen
You can enable it using Control.DoubleBuffered property
No need for picture box, add your own control:
using System.Drawing;
using System.Windows.Forms;
namespace TetrisGame
{
public sealed class TetrisControl : Control
{
private TheBlockType[][] blocks = ...;
protected override void OnPaint(PaintEventArgs e)
{
//draw your stuff here direct to the control, no buffers in the middle
//if that causes flickering, turn on double buffering, but don't bother doing it yourself
//this is your existing draw method:
Draw(e.Graphics);
}
}
}
Then on every tick or movement, do not call paint, just invalidate the control:
tetris.Invalidate();
Also, think out of the box... Rather than doing a full grid scan, you could make each of your shapes part of a linked-list and redraw them based on their position in the grid... Until your grid completely fills, you'd be doing less scanning.
Or, consider only redrawing what you need to redraw, i.e. a block drops near the top, no need to completely redraw the full grid.
Optimisation is what separates us from the animals. Apart from the platypus, who is an optimal creature.
I've modified the SuperContextMenuStrip found at CodeProject to meet some of my projects needs. I'm using it as a tooltip for map markers on a GMap.NET Map Control. Here is a sample of what it looks like:
What I would like to do is pretty this up a little by making it look more like a bubble. Similar to an old Google Maps stytle tooltip:
I've spent some time searching on control transparency and I know this isn't an easy thing. This SO question in particular illustrates that.
I have considered overriding the OnPaint method of the SuperContextMenuStrip to draw a background of the GMap.NET control that is underneath the SuperContextMenuStrip, but even that would fail in cases where the marker is hanging off the GMap.NET control:
What is the correct way to create the type of transparency I am looking for?
In Windows Forms, you achieve transparency (or draw irregularly shaped windows) by defining a region. To quote MSDN
The window region is a collection of pixels within the window where
the operating system permits drawing.
In your case, you should have a bitmap that you will use as a mask. The bitmap should have at least two distinct colors. One of these colors should represent the part of the control that you want to be transparent.
You would then create a region like this:
// this code assumes that the pixel 0, 0 (the pixel at the top, left corner)
// of the bitmap passed contains the color you wish to make transparent.
private static Region CreateRegion(Bitmap maskImage) {
Color mask = maskImage.GetPixel(0, 0);
GraphicsPath grapicsPath = new GraphicsPath();
for (int x = 0; x < maskImage.Width; x++) {
for (int y = 0; y < maskImage.Height; y++) {
if (!maskImage.GetPixel(x, y).Equals(mask)) {
grapicsPath.AddRectangle(new Rectangle(x, y, 1, 1));
}
}
}
return new Region(grapicsPath);
}
You would then set the control’s Region to the Region returned by the CreateRegion method.
this.Region = CreateRegion(YourMaskBitmap);
to remove the transparency:
this.Region = new Region();
As you can probably tell from the code above, creating regions is expensive resource-wise. I'd advice saving regions in variables should you need to use them multiple times. If you use cached regions this way, you'd soon experience another problem. The assignment would work the first time but you would get an ObjectDisposedException on subsequent calls.
A little investigation with refrector would reveal the following code within the set accessor of the Region Property:
this.Properties.SetObject(PropRegion, value);
if (region != null)
{
region.Dispose();
}
The Region object is disposed after use!
Luckily, the Region is clonable and all you need to do to preserve your Region object is to assign a clone:
private Region _myRegion = null;
private void SomeMethod() {
_myRegion = CreateRegion(YourMaskBitmap);
}
private void SomeOtherMethod() {
this.Region = _myRegion.Clone();
}
i have no previous experience in plotting in winforms, in one form i want to plot ecg. or lets say a sin wave or any wave function in a specific area, but what i am doing is e.c.g.. rest of the form will be normal form with buttons and labels,
can anybody be nice enough to through in a tutorial
:)
You have few choices, you can write your own control, that will process data and render it. For more complicated plots, that can be a bit complicated, but the basics are always the same, setting X and Y values ranges and then just draw a line using GDI going from left to right, nothing fancy.
As this can get a bit complicated for more advanced features, you could use some charting controls, I'd read this post or check codeproject.com, I remember, that I saw few attempts to write some decent charting controls, which are open source, new articles will probably be coded in WPF, but you should find something older as well.
Edit:
Some links that you can find useful: Graph plotting lib that's main goal is to simulate ECG or another graph plotting lib
You need to create a custom control.
public class MyECGDrawer : Control{}
In it, you override the OnPaint event
protect override OnPaint(PaintEventArgs pe ){}
Then in the paint function, you draw your graphics the way you want it, let's say sin(x)
// refresh background
pe.Graphics.FillRectangle( Brushes.White, 0, 0, Width, Height );
int prevX = -1, prevY = -1;
for(int x = 0; x < Width; x++ )
{
if( prevX >= 0 )
{
pe.Graphics.DrawLine( Pens.Black, prevX, prevY, x, Math.sin(x) );
}
prevX = x;
prevY = Math.sin(x);
}
To force the ECG to redraw, you call the .Invalidate() function on the control. You should be able to drag and drop the control in your form from the designer.
In the end, the class would look like
public class MyECGDrawer : Control{}
In it, you override the OnPaint event
public class MyECGDrawer : Control
{
protect override OnPaint(PaintEventArgs pe )
{
// refresh background
pe.Graphics.FillRectangle( Brushes.White, 0, 0, Width, Height );
int prevX = -1, prevY = -1;
for(int x = 0; x < Width; x++ )
{
if( prevX >= 0 )
pe.Graphics.DrawLine( Pens.Black, prevX, prevY, x, Math.sin(x) );
prevX = x;
prevY = Math.sin(x);
}
}
}
I wrote up the following and tested it. It seems to do what you want, but note that it is simply plotting sin(x) in a loop with no delay - i.e. the plot for sin(x) streams off the left edge so fast you can hardly see it. You can, however, put a break on any line inside the loop and then step through the loop with F5 to see it work slowly - presumably your streaming ECG data will only arrive at some fixed speed so this should not be a problem in your implementation.
In the following, monitor is a PictureBox on a winforms form. Everything else is local.
private void drawStream(){
const int scaleX = 40;
const int scaleY = 40;
Point monitorTopLeft = new Point(0, 0);
Point MonitorTopLeftMinus1 = new Point(-1, 0);
int halfX = monitor.Width / 2;
int halfY = monitor.Height / 2;
Size size = new Size(halfX + 20, monitor.Height);
Graphics g = monitor.CreateGraphics();
g.TranslateTransform(halfX, halfY);
g.ScaleTransform(scaleX, scaleY);
g.Clear(Color.Black);
g.ResetClip();
float lastY = (float)Math.Sin(0);
float y = lastY;
Pen p = new Pen(Color.White, 0.01F);
float stepX = 1F / scaleX;
for (float x = 0; x < 10; x += stepX) {
g.CopyFromScreen(monitor.PointToScreen(monitorTopLeft), MonitorTopLeftMinus1, size, CopyPixelOperation.SourceCopy);
y = (float)Math.Sin(x);
g.DrawLine(p, -stepX, lastY, 0, y);
lastY = y;
}
}
Some additional info that may be helpful:
The origin in a picture box starts
out at the top left corner.
TranslateTransform allows you to
translate (i.e. move) the origin.
In the example, I translate it by
half the picture box's width and
half its height.
ScaleTransform changes the magnification of the picturebox - note that it even magnifies the width of the pen used to draw on the picturebox - this is why the pen's width is set to 0.01.
CopyFromScreen performs a bitblt. Its source point is relative to the screen, the destination is relative to the picturebox and the size of the rectangle to move disregards any transforms (like the scale and translation transforms we added).
Notice that the X coordinates in the DrawLine method are -stepx and 0. All drawing basically occurs right on the y axis (i.e. x = 0) and then CopyFromScreen moves the drawn portion to the left so that it "streams" off to the left.
Unless you are doing this as a learning experience, you may want to consider looking at the free Microsoft Chart Controls for .NET available here.
http://www.microsoft.com/downloads/details.aspx?FamilyID=130f7986-bf49-4fe5-9ca8-910ae6ea442c&displaylang=en#QuickInfoContainer
That being said, I would offer the following guidelines if you want to roll your own.
Create a user control to encapsulate the plot rendering rather than render directly on the form.
In your control, expose properties to get/set the data you wish to render and add any other properties you want to control the rendering (scaling, panning, colors, etc.)
In you control, either override the OnPaint method or create an event handler for the Paint event. These methods will have a PaintEventArgs object passed to them, which contains a Graphics object as a property. The methods of the Graphics object are used to render points, lines, etc onto the control when it needs to be painted. Most of the drawing operations require either a pen (outlines / lines) or a brush (filled areas). You can use stock objects for these operations (e.g. Pens.Black or Brushes.Red) or you can create your own (see documentation). If you create you own objects, make sure you dispose of them after using them (e.g. using the "using" statement or by calling Dispose).
There are a couple good books on GDI+. I suggest picking one up if you are going in deep.