On a winform, is there a way to draw a rectangle like this (without top) using the DrawRectangle method or a workaround?
You can try using the SetClip method:
private void DrawTopless(Graphics g, Rectangle r) {
g.SetClip(new Rectangle(r.Left, r.Top, r.Width + 1, 10), CombineMode.Exclude);
g.DrawRectangle(Pens.Red, r);
g.ResetClip();
}
Related
I have problem with GDI. I do it in WinForms. There is what I got:
And there is my code:
Graphics phantom = this.pictureBox1.CreateGraphics();
Pen blackPen = new Pen(Color.Black, 3);
Rectangle rect = new Rectangle(0, 0, 200, 150);
float startAngle = 180F;
float sweepAngle = 180F;
phantom.DrawArc(blackPen, rect, startAngle, sweepAngle);
phantom.Dispose();
I want to get something like that:
Really sorry for my paint skills. Is it possible to create such a thing from the arc itself or do I have to do it from an ellipse? I don't know how to go about it. Any tips are welcome. Thanks.
From my comments on the original post:
You have two circles, let's call them lower and upper. Define the
upper circle as a GraphicsPath and pass that to the constructor of a
Region. Now pass that Region to e.Graphics via the ExcludeClip method.
Now draw the lower circle, which will be missing the top part because
of the clipping. Next, Reset() the Graphics and define the lower
circle in a GraphicsPath. Use Graphics.Clip() this time, and chase
that with drawing the upper circle. It will only be visible where the
lower circle clip was.
Proof of concept:
Code:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
Graphics phantom = e.Graphics;
using (Pen blackPen = new Pen(Color.Black, 3))
{
Rectangle upper = new Rectangle(-50, -250, 300, 300);
GraphicsPath upperGP = new GraphicsPath();
upperGP.AddEllipse(upper);
using (Region upperRgn = new Region(upperGP))
{
Rectangle lower = new Rectangle(0, 0, 200, 150);
GraphicsPath lowerGP = new GraphicsPath();
lowerGP.AddEllipse(lower);
float startAngle = 180F;
float sweepAngle = 180F;
phantom.ExcludeClip(upperRgn);
phantom.DrawArc(blackPen, lower, startAngle, sweepAngle);
phantom.ResetClip();
phantom.SetClip(lowerGP);
phantom.DrawEllipse(blackPen, upper);
}
}
}
I'm generating a bunch of RectangleF objects having different sizes and positions. What would be the best way to fill them with a gradient Brush in GDI+?
In WPF I could create a LinearGradientBrush, set Start and End relative points and WPF would take care of the rest.
In GDI+ however, the gradient brush constructor requires the position in absolute coordinates, which means I have to create a Brush for each of the rectangle, which would be a very complex operation.
Am I missing something or that's indeed the only way?
You can specify a transform at the moment just before the gradient is applied if you would like to declare the brush only once. Note that using transformations will override many of the constructor arguments that can be specified on a LinearGradientBrush.
LinearGradientBrush.Transform Property (System.Drawing.Drawing2D)
To modify the transformation, call the methods on the brush object corresponding to the desired matrix operations. Note that matrix operations are not commutative, so order is important. For your purposes, you'll probably want to do them in this order for each rendition of your rectangles: Scale, Rotate, Offset/Translate.
LinearGradientBrush.ResetTransform Method # MSDN
LinearGradientBrush.ScaleTransform Method (Single, Single, MatrixOrder) # MSDN
LinearGradientBrush.RotateTransform Method (Single, MatrixOrder) # MSDN
LinearGradientBrush.TranslateTransform Method (Single, Single, MatrixOrder) # MSDN
Note that the system-level drawing tools don't actually contain a stock definition for gradient brush, so if you have performance concerns about making multiple brushes, creating a multitude of gradient brushes shouldn't cost any more than the overhead of GDI+/System.Drawing maintaining the data required to define the gradient and styling. You may be just as well off to create a Brush per rectangle as needed, without having to dive into the math required to customize the brush via transform.
Brush Functions (Windows) # MSDN
Here is a code example you can test in a WinForms app. This app paints tiles with a gradient brush using a 45 degree gradient, scaled to the largest dimension of the tile (naively calculated). If you fiddle with the values and transformations, you may find that it isn't worth using the technique setting a transform for all of your rectangles if you have non-trivial gradient definitions. Otherwise, remember that your transformations are applied at the world-level, and in the GDI world, the y-axis is upside down, whereas in the cartesian math world, it is ordered bottom-to-top. This also causes the angle to be applied clockwise, whereas in trigonometry, the angle progresses counter-clockwise in increasing value for a y-axis pointing up.
using System.Drawing.Drawing2D;
namespace TestMapTransform
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
Rectangle rBrush = new Rectangle(0,0,1,1);
Color startColor = Color.DarkRed;
Color endColor = Color.White;
LinearGradientBrush br = new LinearGradientBrush(rBrush, startColor, endColor, LinearGradientMode.Horizontal);
int wPartitions = 5;
int hPartitions = 5;
int w = this.ClientSize.Width;
w = w - (w % wPartitions) + wPartitions;
int h = this.ClientSize.Height;
h = h - (h % hPartitions) + hPartitions;
for (int hStep = 0; hStep < hPartitions; hStep++)
{
int hUnit = h / hPartitions;
for (int wStep = 0; wStep < wPartitions; wStep++)
{
int wUnit = w / wPartitions;
Rectangle rTile = new Rectangle(wUnit * wStep, hUnit * hStep, wUnit, hUnit);
if (e.ClipRectangle.IntersectsWith(rTile))
{
int maxUnit = wUnit > hUnit ? wUnit : hUnit;
br.ResetTransform();
br.ScaleTransform((float)maxUnit * (float)Math.Sqrt(2d), (float)maxUnit * (float)Math.Sqrt(2d), MatrixOrder.Append);
br.RotateTransform(45f, MatrixOrder.Append);
br.TranslateTransform(wUnit * wStep, hUnit * hStep, MatrixOrder.Append);
e.Graphics.FillRectangle(br, rTile);
br.ResetTransform();
}
}
}
}
private void Form1_Resize(object sender, EventArgs e)
{
this.Invalidate();
}
}
}
Here's a snapshot of the output:
I recommend you to create a generic method like this:
public void Paint_rectangle(object sender, PaintEventArgs e)
{
RectangleF r = new RectangleF(0, 0, e.ClipRectangle.Width, e.ClipRectangle.Height);
if (r.Width > 0 && r.Height > 0)
{
Color c1 = Color.LightBlue;
Color c2 = Color.White;
Color c3 = Color.LightBlue;
LinearGradientBrush br = new LinearGradientBrush(r, c1, c3, 90, true);
ColorBlend cb = new ColorBlend();
cb.Positions = new[] { 0, (float)0.5, 1 };
cb.Colors = new[] { c1, c2, c3 };
br.InterpolationColors = cb;
// paint
e.Graphics.FillRectangle(br, r);
}
}
then, for every rectangle just call:
yourrectangleF.Paint += new PaintEventHandler(Paint_rectangle);
If the gradrients colors are all the same, you can make that method shorter. Hope that helped..
I've encountered a weird behavior when trying to use a thumb to move a control around on a canvas. When I add a control to a canvas and use Thumb DragDelta event to move it around everything looks good. But when I apply a rotate transform to the control dragging it around is bizarre. The control starts to circle around the cursor, and the bigger the angle the bigger the circle.
Does anyone know how to make thumb work with a transformed element? I've spent all day trying to figure it out and nothing smart is coming to my mind.
Thanks for your help!
If you apply any rotating transform to FrameworkElement it means that the coordinates grid associated with it has rotated.
Thus, any event handler of this FrameworkElement will be receive position values in own coordinates grid.
void DragThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
//You can use this values when RotateTransform is null
double deltaHorizontal = e.HorizontalChange;
double deltaVertical = e.VerticalChange;
//Transform coordinates
Vector v = Math2DHelper.RotateVector2d(e.HorizontalChange, e.VerticalChange, Math2DHelper.D2R(rotationInDegrees));
//Right values
deltaHorizontal = v.X;
deltaVertical = v.Y;
...
}
Sample math2D helper
public static class Math2DHelper
{
public static Vector RotateVector2d(double x0, double y0, double rad)
{
Vector result = new Vector();
result.X = x0 * Math.Cos(rad) - y0 * Math.Sin(rad);
result.Y = x0 * Math.Sin(rad) + y0 * Math.Cos(rad);
return result;
}
public static double D2R(double degree)
{
return (degree%360)*Math.PI/180;
}
}
I came across this problem and found the solution , maybe someone find it helpful ,you need to check for any transformation before drag thumb
private void Thumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
var thumb = dts as UIElement;
var transform = thumb.RenderTransform as RotateTransform;
Point dragDelta = new Point(e.HorizontalChange, e.VerticalChange);
if (transform != null)
{
dragDelta = transform.Transform(dragDelta);
}
Canvas.SetLeft(thumb, Canvas.GetLeft(thumb) + dragDelta.X);
Canvas.SetTop(thumb, Canvas.GetTop(thumb) + dragDelta.Y);
}
If you ditch the Canvas properties and apply the movement in the right order in a TransformGroup it should work:
<Thumb.RenderTransform>
<TransformGroup>
<TranslateTransform x:Name="translation" />
<RotateTransform ... />
</TransformGroup>
</Thumb.RenderTransform>
translation.X += e.HorizontalChange;
translation.Y += e.VerticalChange;
If you switch the order in the group you get the same behavior as when using Canvas.Left/Top.
(If you animated the rotation this will not help you)
It appears that Thumb's HorizontalChange and VerticalChange don't play nicely when thumb is rotated. So, I'm just using cursor location in the canvas get my left and top offsets. Its not exactly accurate, but its close enough for what I'm trying to do.
I want to draw like in the old qbasik, where you can into 5 lines and PSET (x, y) derive any graph, or Lissajous figures.
Question: what better way to go for WPF? and way for XNA?
Any samples?
For WPF and Silverlight
WriteableBitmap
http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.writeablebitmap.aspx
WriteableBitmapEx library. Tries to compensate that with extensions methods that are easy to use like built in methods and offer GDI+ like functionality:
http://writeablebitmapex.codeplex.com/
In XNA this isn't the most efficient thing in general, but I think your best bet is to probably create a texture and set each pixel using SetData, and render it to the screen with SpriteBatch.
SpriteBatch spriteBatch;
Texture2D t;
Color[] blankScreen;
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
//initialize texture
t = new Texture2D(GraphicsDevice, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, false, SurfaceFormat.Color);
//clear screen initially
blankScreen = new Color[GraphicsDevice.Viewport.Width * GraphicsDevice.Viewport.Height];
for (int i = 0; i < blankScreen.Length; i++)
{
blankScreen[i] = Color.Black;
}
ClearScreen();
}
private void Set(int x, int y, Color c)
{
Color[] cArray = { c };
//unset texture from device
GraphicsDevice.Textures[0] = null;
t.SetData<Color>(0, new Rectangle(x, y, 1, 1), cArray, 0, 1);
//reset
GraphicsDevice.Textures[0] = t;
}
private void ClearScreen()
{
//unset texture from device
GraphicsDevice.Textures[0] = null;
t.SetData<Color>(blankScreen);
//reset
GraphicsDevice.Textures[0] = t;
}
protected override void Draw(GameTime gameTime)
{
spriteBatch.Begin();
spriteBatch.Draw(t, Vector2.Zero, Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
With this you can call either Set or ClearScreen at will in your Update or Draw. You may have to play with the texture index (I just used 0 for this example, might not be it for you), and also you only need to unset / reset one time per frame, so you can optimize that depending on how you use them.
I have a custom WPF Canvas, upon which I would like to show a grid. I do so by overriding the OnRender method on Canvas, and using the DrawingContext drawing functions. IsGridVisible, GridWidth, GridHeight are the number of pixels between each grid line horizontally and vertically respectively.
I also use a ScaleTransform on the Canvas.LayoutTransform property to zoom the Canvas items and as one expects, the grid line thicknesses are multiplied by the ScaleTransform scaling factors as shown in the below image. Is there any way to draw single pixel lines, irrespective of the current Canvas RenderTransform?
protected override void OnRender(System.Windows.Media.DrawingContext dc)
{
base.OnRender(dc);
if (IsGridVisible)
{
// Draw GridLines
Pen pen = new Pen(new SolidColorBrush(GridColour), 1);
pen.DashStyle = DashStyles.Dash;
for (double x = 0; x < this.ActualWidth; x += this.GridWidth)
{
dc.DrawLine(pen, new Point(x, 0), new Point(x, this.ActualHeight));
}
for (double y = 0; y < this.ActualHeight; y += this.GridHeight)
{
dc.DrawLine(pen, new Point(0, y), new Point(this.ActualWidth, y));
}
}
}
alt text http://www.freeimagehosting.net/uploads/f05ad1f602.png
As the comments to the original post state. The Pen thickness should be set to 1.0/zoom.