问题
I'm trying to figure out how exactly gdi+ graphics containers works with different graphic units. Take a look at the below code. It compiles, you can paste it into a fresh new form.
void Form2_Paint(object sender, PaintEventArgs e)
{
var gfx = e.Graphics;
System.Diagnostics.Debug.WriteLine("DpiX={0}, DpiY={1}", gfx.DpiX, gfx.DpiY);
gfx.PageUnit = GraphicsUnit.Inch;
var pen = new Pen(Color.Black, 0.01f);
// Create outer container, 2 inches in size with X and Y set to 0.1 inches
var outerContainer = gfx.BeginContainer(
new RectangleF(0.1f, 0.1f, 2, 2),
new RectangleF(0, 0, 2, 2),
GraphicsUnit.Pixel);
// Draw the outer rectangle
gfx.DrawRectangle(pen, new Rectangle(0, 0, 2, 2));
// Create inner container, 1 inch in size with X and Y set to 0.1 inches
var innerContainer = gfx.BeginContainer(
new RectangleF(0.1f, 0.1f, 1, 1),
new RectangleF(0, 0, 1, 1),
GraphicsUnit.Pixel);
// Draw the inner rectangle
gfx.DrawRectangle(pen, new Rectangle(0, 0, 1, 1));
gfx.EndContainer(innerContainer);
gfx.EndContainer(outerContainer);
}
Above code is a pretty simple example of nested graphic containers, I used no scaling transformation. This is how the form looks like when above paint handler is used:
It's pretty simple. Now, I will try to describe what the problem is.
This is a signature of the BeginContainer
method:
public GraphicsContainer BeginContainer(
RectangleF dstrect,
RectangleF srcrect,
GraphicsUnit unit
)
What I'm failing to understand is GraphicsUnit unit
argument.
From MSDN:
Member of the GraphicsUnit enumeration that specifies the unit of measure for the container.
This appears not to be true!
As you can see in my code I'm using Inch units as such: gfx.PageUnit = GraphicsUnit.Inch
.
But, when I'm creating containers this is what I'm passing as units argument to the BeginContainer
method: GraphicsUnit.Pixel
. What happens after the container is created? Inches are being used (what I actually want). But if I pass GraphicsUnit.Inch
(or millimeters or anything else) to the argument, pixels are used. So, it seems that in order to accomplish what I want (use inches) I have to specify pixels?
This makes no sense to me. You can try changing units in BeginContainer
methods in above code and observe strange results. I've read the MSDN and everything I could gather on this but I am still clueless.
I'm writing software which draws a lot of stuff using gdi+ and it's using millimeter units for printing purposes - when I started using containers I was quite surprised that I apparently need to specify pixels as units. I'm really suspicious of any printing code where pixels are mentioned. It must be that I have a big misunderstanding of this matter.
So, all above considered, my question is: what is the purpose of the unit
argument in this method?
回答1:
Unfortunately, GDI+ is as far as I can tell one of the most poorly documented APIs in Windows. Some browsing on the web turns up very few people really using it much, and no insight into your question.
Even more unfortunately, the GDI+ API was basically copied straight over to the .NET Graphics
object. Even the documentation was for the most part just copied verbatim. Note the similarities between the .NET Graphics.BeginContainer Method (RectangleF, RectangleF, GraphicsUnit) and the Windows Graphics.BeginContainer(const RectF, const RectF, Unit) method pages.
To further complicate matters, there is this blog entry which tantalizingly reads:
Another example is the Save and BeginContainer methods in Graphics class, which perform very differently, yet have the same MSDN documentation that fails to differentiate between the two calls
…but fails to go into any detail as to how these two methods are in fact different.
Now, all that said, with some experimentation, I think I've decoded the parameters and behavior:
- The
unit
parameter appears to be used to specify the units used for thesrcrect
parameter - The
dstrect
parameters is specified in the units currently in use for theGraphics
object - The new container winds up with its
Graphics.PageUnit
set back to the default value ofGraphicsUnit.Display
I could not find any hint of the first two points above in the documentation. It was only through experimentation and careful observation that I learned that, and frankly without actual documentation to back my conclusion up, I'm still not 100% sure about it.
There is a hint of the third point in the documentation of the BeginContainer()
method:
The graphics state established by the BeginContainer method includes the rendering qualities of the default graphics state; any rendering-quality state changes existing when the method is called are reset to the default values.
At first glance, this sentence seems to be saying that only the "rendering-quality state changes" are reset. But reading more carefully, one sees that part of the state is simply being called out in particular, as it is included in all of the state that is reset. Granted, a more careful reading does not gain oneself any additional understanding :(, but at least one can see the sentence shouldn't be taken as the last word on everything that's reset.
So the key to getting things to work correctly would be to specify the units you want to work in (e.g. GraphicsUnit.Inch
) at every step of the way: in the initial settings before you create a container, in the call to BeginContainer()
(but here, only to control the way srcrect
is being interpreted), and then finally just after BeginContainer()
, setting the Graphics.PageUnit
property again.
When I did it that way, I was able to use whatever units I wanted to drawing. I could even mix and match, though of course that resulted in some non-intuitive values being passed for the container rectangles as compared to the rectangles I was drawing.
As an example, here's a snippet where I use inches for the initial Graphics
state, and millimeters for the container:
gfx.PageUnit = GraphicsUnit.Inch;
using (Pen blackPen = new Pen(Color.Black, 0.01f))
using (Pen redPen = new Pen(Color.Red, 0.01f))
{
gfx.DrawRectangle(blackPen, .25f, .25f, 2, 2);
var outerContainer = gfx.BeginContainer(
new RectangleF(.25f, .25f, 2, 2),
new RectangleF(0, 0, 2 * 25.4f, 2 * 25.4f),
GraphicsUnit.Millimeter);
gfx.PageUnit = GraphicsUnit.Millimeter;
gfx.DrawRectangle(redPen, .25f * 25.4f, .25f * 25.4f, 1.5f * 25.4f, 1.5f * 25.4f);
gfx.EndContainer(outerContainer);
}
That produces this image:
So I'm able to draw a 2x2 inch rectangle outside the container, and then inside the container I draw a 1.5x1.5 inch rectangle, but do so using millimeters (converting explicitly in the parameters, just to make it clearer to myself what I'm doing).
回答2:
Perhaps, you don't need to care about at all.
I failed to pass the right arguments to Graphics.BeginContainer
as well.
However, using the Transform
methods (ResetTransform
in the end) worked perfectly for the problem I had to solve.
So, first consider the Transform
methods before trying to understand containers.
来源:https://stackoverflow.com/questions/29170453/how-do-graphic-containers-work