I'm able to draw on the image using the below code. But my problem is, it is not a continuous line. It looks somehow broken. See below image for better understanding.
My XAML :
<Grid x:Name="Gridimage1" Grid.Column="0">
<Image Name="image1" HorizontalAlignment="Left" VerticalAlignment="Top" Stretch="Fill" >
</Image>
</Grid>
My c# code :
#region "Drawing on image"
static WriteableBitmap writeableBitmap;
static Image i;
public void DrawingOnImage() // this function will be called after image load
{
if (image1.Source != null)
{
i = new Image();
RenderOptions.SetBitmapScalingMode(image1, BitmapScalingMode.NearestNeighbor);
RenderOptions.SetEdgeMode(image1, EdgeMode.Aliased);
BitmapSource BitmapSrc = new FormatConvertedBitmap(image1.Source as BitmapSource, PixelFormats.Default, null, 0);
//writeableBitmap = new WriteableBitmap((int)image1.ActualWidth, (int)image1.ActualHeight, 96, 96, PixelFormats.Bgr32, null);
writeableBitmap = new WriteableBitmap(BitmapSrc);
image1.Source = writeableBitmap;
//image1.Stretch = Stretch.None;
image1.HorizontalAlignment = HorizontalAlignment.Left;
image1.VerticalAlignment = VerticalAlignment.Top;
i = image1;
image1.MouseMove += new MouseEventHandler(i_MouseMove);
image1.MouseLeftButtonDown +=
new MouseButtonEventHandler(i_MouseLeftButtonDown);
image1.MouseRightButtonDown +=
new MouseButtonEventHandler(i_MouseRightButtonDown);
}
}
static void i_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DrawPixel(e);
}
static void i_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
DrawPixel(e);
}
}
static void DrawPixel(MouseEventArgs e)
{
double CR = i.Source.Height / i.ActualHeight;
double RR = i.Source.Width / i.ActualWidth;
int column = (int)(e.GetPosition(i).X * RR) ;
int row = (int)(e.GetPosition(i).Y * CR);
// Reserve the back buffer for updates.
writeableBitmap.Lock();
unsafe
{
// Get a pointer to the back buffer.
int pBackBuffer = (int)writeableBitmap.BackBuffer;
// Fin d the address of the pixel to draw.
pBackBuffer += row * writeableBitmap.BackBufferStride;
pBackBuffer += column * 4;
// Compute the pixel's color.
int color_data = 255 << 16; // R
color_data |= 128 << 8; // G
color_data |= 255 << 0; // B
// Assign the color data to the pixel.
*((int*)pBackBuffer) = color_data;
}
// Specify the area of the bitmap that changed.
writeableBitmap.AddDirtyRect(new Int32Rect(column, row, 1, 1));
// Release the back buffer and make it available for display.
writeableBitmap.Unlock();
}
#endregion
Image output :
You can see the line (pink color) I have drawn. It's not a continuous line. Where am I failing?
Update :
My findings after #loxxy's Inputs.Set oldx ,oldy to zero initialy.
if (oldx == 0 && oldy == 0)
{
writeableBitmap.DrawLineAa(column, row, column, row, SelectedColor);
}
else
{
if (Math.Abs(oldx - column) > 10 || Math.Abs(oldy - row) > 10)
{
writeableBitmap.DrawLineAa(column, row, column, row, SelectedColor);
}
else
{
writeableBitmap.DrawLineAa(column, row, oldx, oldy, SelectedColor);
}
}
oldx = column;
oldy = row;
Since you are drawing it pixel by pixel, the mouse has to update the coordinates at a much faster rate.
And I believe it relates to the DPI of the mouse... And you can do nothing about that.
So instead try drawing a line. So something like this in WPF:
WriteableBitmapExtensions.DrawLine
The mouse events you receive will not return consecutive points. If you move the mouse fast, the points returned will not be next to each other. If you want to draw a line under the mouse cursor, you need to draw a line from the previous point drawn to the current point on every MouseMove or MouseButtonDown callback.
Related
In my WinForm project I have a DataGridView control.
I achieved to merge the Header cells of 2 Columns. It is working fine but there is a problem when scrolling. The image below should explain it.
Can anyone help?
This is the code I use to merging the Column Header cells.
The indexes of the merged Columns are 20 and 21.
private void loadEvents()
{
this.dgv_db_door.Paint += new PaintEventHandler(dgv_db_door_Paint);
this.dgv_db_door.Scroll += new ScrollEventHandler(dgv_db_door_Scroll);
this.dgv_db_door.ColumnWidthChanged += DataGridView1_ColumnWidthChanged;
this.dgv_db_door.Resize += DataGridView1_Resize;
}
private void dgv_db_door_Paint(object sender, PaintEventArgs e)
{
string doorCloser = "DOOR CLOSER";
//Index numbers of merged columns ara 20 and 21;
int mergedColumn1 = 20;
int mergedColumn2 = 21;
Rectangle r1 = dgv_db_door.GetCellDisplayRectangle(mergedColumn1, -1, true);
int w2 = dgv_db_door.GetCellDisplayRectangle(mergedColumn2, -1, true).Width;
r1.X += 1;
r1.Y += 1;
r1.Width = r1.Width + w2 - 2;
r1.Height = r1.Height / 2 - 2;
e.Graphics.FillRectangle(new SolidBrush(dgv_db_door.ColumnHeadersDefaultCellStyle.BackColor), r1);
StringFormat format = new StringFormat();
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
e.Graphics.DrawString(doorCloser, dgv_db_door.ColumnHeadersDefaultCellStyle.Font,
new SolidBrush(dgv_db_door.ColumnHeadersDefaultCellStyle.ForeColor), r1, format);
}
private void dgv_db_door_Scroll(object sender, ScrollEventArgs e)
{
if (e.OldValue > e.NewValue)
{
}
else
{
this.InvalidateHeader();
}
}
private void DataGridView1_Resize(object sender, EventArgs e)
{
this.InvalidateHeader();
}
private void DataGridView1_ColumnWidthChanged(object sender, DataGridViewColumnEventArgs e)
{
this.InvalidateHeader();
}
private void InvalidateHeader()
{
Rectangle rtHeader = this.dgv_db_door.DisplayRectangle;
rtHeader.Height = this.dgv_db_door.ColumnHeadersHeight / 2;
this.dgv_db_door.Invalidate(rtHeader);
}
A few modifications to the drawing procedure:
The custom Header drawing Rectangle is sized calculating the Width of all the Columns in the specified Range.
The position of the drawing area is calculated using the left-most Column's position, the RowHeadersWidth and the current DataGridView.HorizontalScrollingOffset, which defines the horizontal scroll position of the client rectangle.
To draw the custom Header, a clipping region is created - using Graphics.SetClip() - to exclude the Row Header from the drawing area.
The custom Header is only rendered when visible on screen.
I'm using TextRenderer.DrawText instead of Graphics.DrawString(), since this is the method used by this control to render its content.
Hence, I'm calculating the height of the text bounds using TextRenderer.MeasureText()
TextFormatFlags.PreserveGraphicsClipping is used to instruct TextRenderer not to draw the text outside the clipping region of the Graphics object.
Note 1: I've added Double-Buffering to the DataGridView, to avoid any flickering when clicking the Header Cells. It may have an impact when the grid needs to render a high number of Rows.
Note 2: You can remove all those InvalidateHeader() calls, not needed here.
► This new behavior allows to reset the range of Columns to include in the custom Header and the Header's Text at any time (as shown in the visual example).
This is how it looks now:
using System.Reflection;
TextFormatFlags centerTopflags =
TextFormatFlags.HorizontalCenter | TextFormatFlags.Top | TextFormatFlags.PreserveGraphicsClipping;
string mergedHeaderText = string.Empty;
int[] mergedColumns = null;
public void SomeForm()
{
this.InitializeComponent();
var flags = BindingFlags.Instance | BindingFlags.NonPublic;
dgvTest.GetType().GetProperty("DoubleBuffered", flags).SetValue(dgvTest, true);
mergedColumns = new int[] { 20, 21 };
mergedHeaderText = "DOOR CLOSER"
}
private void dgv_db_door_Paint(object sender, PaintEventArgs e)
{
var dgv = sender as DataGridView;
var headerStyle = dgv.ColumnHeadersDefaultCellStyle;
int colsWidth = -1;
int colsLeft = 1;
// Absolute Width of the merged Column range
for (int i = 0; i < mergedColumns.Length; i++) {
var col = dgv.Columns[mergedColumns[i]];
colsWidth += col.Visible ? col.Width : 0;
}
// Absolute Left position of the first Column to merge
if (mergedColumns[0] > 0) {
colsLeft += dgv.Columns.OfType<DataGridViewColumn>()
.Where(c => c.Visible).Take(mergedColumns[0]).Sum(c => c.Width);
}
// Merged Headers raw drawing Rectangle
var r = new Rectangle(
dgv.RowHeadersWidth + colsLeft - dgv.HorizontalScrollingOffset, 2,
colsWidth, dgv.ColumnHeadersHeight);
// Measure the Height of the text to render - no wrapping
r.Height = TextRenderer.MeasureText(e.Graphics, mergedHeaderText, headerStyle.Font, r.Size, centerTopflags).Height;
// Draw the merged Headers only if visible on screen
if (r.Right > dgv.RowHeadersWidth || r.X < dgv.DisplayRectangle.Right) {
// Clip the drawing Region to exclude the Row Header
var clipRect = new Rectangle(
dgv.RowHeadersWidth + 1, 0,
dgv.DisplayRectangle.Width - dgv.RowHeadersWidth, dgv.ColumnHeadersHeight);
e.Graphics.SetClip(clipRect);
using (var brush = new SolidBrush(headerStyle.BackColor)) e.Graphics.FillRectangle(brush, r);
TextRenderer.DrawText(e.Graphics, mergedHeaderText, headerStyle.Font, r, headerStyle.ForeColor, centerTopflags);
e.Graphics.ResetClip();
}
}
My program has a problem with poor coordination between the depth and color images.
The player mask is not in the same place as the person (see the picture below).
void _AllFreamReady(object sender, AllFramesReadyEventArgs e)
{
using (ColorImageFrame _colorFrame = e.OpenColorImageFrame())
{
if (_colorFrame == null) //jezeli pusta ramka nie rob nic
{
return;
}
byte[] _pixels = new byte[_colorFrame.PixelDataLength]; //utworzenie tablicy pixeli dla 1 ramki obrazu o rozmiarach przechwyconej ramki z strumienia
_colorFrame.CopyPixelDataTo(_pixels); //kopiujemy pixele do tablicy
int _stride = _colorFrame.Width * 4; //Kazdy pixel moze miec 4 wartosci Red Green Blue lub pusty
image1.Source =
BitmapSource.Create(_colorFrame.Width, _colorFrame.Height,
96, 96, PixelFormats.Bgr32, null, _pixels, _stride);
if (_closing)
{
return;
}
using (DepthImageFrame _depthFrame = e.OpenDepthImageFrame())
{
if (_depthFrame == null)
{
return;
}
byte[] _pixelsdepth = _GenerateColoredBytes(_depthFrame,_pixels);
int _dstride = _depthFrame.Width * 4;
image3.Source =
BitmapSource.Create(_depthFrame.Width, _depthFrame.Height,
96, 96, PixelFormats.Bgr32, null, _pixelsdepth, _dstride);
}
}
}
private byte[] _GenerateColoredBytes(DepthImageFrame _depthFrame, byte[] _pixels)
{
short[] _rawDepthData = new short[_depthFrame.PixelDataLength];
_depthFrame.CopyPixelDataTo(_rawDepthData);
Byte[] _dpixels = new byte[_depthFrame.Height * _depthFrame.Width * 4];
const int _blueindex = 0;
const int _greenindex = 1;
const int _redindex = 2;
for (int _depthindex = 0, _colorindex = 0;
_depthindex < _rawDepthData.Length && _colorindex < _dpixels.Length;
_depthindex++, _colorindex += 4)
{
int _player = _rawDepthData[_depthindex] & DepthImageFrame.PlayerIndexBitmaskWidth;
if (_player > 0)
{
_dpixels[_colorindex + _redindex] = _pixels[_colorindex + _redindex];
_dpixels[_colorindex + _greenindex] = _pixels[_colorindex + _greenindex];
_dpixels[_colorindex + _blueindex] = _pixels[_colorindex + _blueindex];
};
}
return _dpixels;
}
RGB and depth data are not aligned. This is due to the position of depth sensor and RGB camera in the Kinect case: they are different, so you cannot expect aligned images using different points of view.
However, you problem is quite common, and was solved by the KinectSensor.MapDepthFrameToColorFrame, that was deprecated after SDK 1.6. Now, what you need is the CoordinateMapper.MapDepthFrameToColorFrame method.
The Coordinate Mapping Basics-WPF C# Sample shows how to use this method. You can find some significant parts of the code in the following:
// Intermediate storage for the depth data received from the sensor
private DepthImagePixel[] depthPixels;
// Intermediate storage for the color data received from the camera
private byte[] colorPixels;
// Intermediate storage for the depth to color mapping
private ColorImagePoint[] colorCoordinates;
// Inverse scaling factor between color and depth
private int colorToDepthDivisor;
// Format we will use for the depth stream
private const DepthImageFormat DepthFormat = DepthImageFormat.Resolution320x240Fps30;
// Format we will use for the color stream
private const ColorImageFormat ColorFormat = ColorImageFormat.RgbResolution640x480Fps30;
//...
// Initialization
this.colorCoordinates = new ColorImagePoint[this.sensor.DepthStream.FramePixelDataLength];
this.depthWidth = this.sensor.DepthStream.FrameWidth;
this.depthHeight = this.sensor.DepthStream.FrameHeight;
int colorWidth = this.sensor.ColorStream.FrameWidth;
int colorHeight = this.sensor.ColorStream.FrameHeight;
this.colorToDepthDivisor = colorWidth / this.depthWidth;
this.sensor.AllFramesReady += this.SensorAllFramesReady;
//...
private void SensorAllFramesReady(object sender, AllFramesReadyEventArgs e)
{
// in the middle of shutting down, so nothing to do
if (null == this.sensor)
{
return;
}
bool depthReceived = false;
bool colorReceived = false;
using (DepthImageFrame depthFrame = e.OpenDepthImageFrame())
{
if (null != depthFrame)
{
// Copy the pixel data from the image to a temporary array
depthFrame.CopyDepthImagePixelDataTo(this.depthPixels);
depthReceived = true;
}
}
using (ColorImageFrame colorFrame = e.OpenColorImageFrame())
{
if (null != colorFrame)
{
// Copy the pixel data from the image to a temporary array
colorFrame.CopyPixelDataTo(this.colorPixels);
colorReceived = true;
}
}
if (true == depthReceived)
{
this.sensor.CoordinateMapper.MapDepthFrameToColorFrame(
DepthFormat,
this.depthPixels,
ColorFormat,
this.colorCoordinates);
// ...
int depthIndex = x + (y * this.depthWidth);
DepthImagePixel depthPixel = this.depthPixels[depthIndex];
// scale color coordinates to depth resolution
int X = colorImagePoint.X / this.colorToDepthDivisor;
int Y = colorImagePoint.Y / this.colorToDepthDivisor;
// depthPixel is the depth for the (X,Y) pixel in the color frame
}
}
I am working on this problem myself. I agree with VitoShadow that one solution is in the coordinate mapping, but a section not posted where the ratio between the miss matched depth and color screen resolutions(this.colorToDepthDivisor = colorWidth / this.depthWidth;). This is used with a shift of the data (this.playerPixelData[playerPixelIndex - 1] = opaquePixelValue;) to account for the miss match.
Unfortunately, this can create a border around the masked image where the depthframe isn't stretched to the edge of the color frame. I am trying to not use skeleton mapping and am optimizing my code by tracking depthdata with emgu cv to pass a point as the center of the ROI of the colorframe. I am still working on it.
I have implemented a picturebox in my in my form.I even implemented the scrollbars to make the image fit in it.so now the problem is when i try to scroll the button down,it scrolls down and immediately when i leave the mouse the button scrolls up ..here is the code implemnted please give some suggestions..
public void DisplayScrollBars()
{
// If the image is wider than the PictureBox, show the HScrollBar.
if (pictureBox1.Width > pictureBox1.Image.Width - this.vScrollBar1.Width)
{
hScrollBar1.Visible = false;
}
else
{
hScrollBar1.Visible = true;
}
// If the image is taller than the PictureBox, show the VScrollBar.
if (pictureBox1.Height >
pictureBox1.Image.Height - this.hScrollBar1.Height)
{
vScrollBar1.Visible = false;
}
else
{
vScrollBar1.Visible = true;
}
}
private void HandleScroll(Object sender, ScrollEventArgs se)
{
/* Create a graphics object and draw a portion
of the image in the PictureBox. */
Graphics g = pictureBox1.CreateGraphics();
g.DrawImage(pictureBox1.Image,
new Rectangle(0, 0, pictureBox1.Right - vScrollBar1.Width,
pictureBox1.Bottom - hScrollBar1.Height),
new Rectangle(hScrollBar1.Value, vScrollBar1.Value,
pictureBox1.Right - vScrollBar1.Width,
pictureBox1.Bottom - hScrollBar1.Height),
GraphicsUnit.Pixel);
pictureBox1.Update();
}
public void SetScrollBarValues()
{
// Set the Maximum, Minimum, LargeChange and SmallChange properties.
this.vScrollBar1.Minimum = 0;
this.hScrollBar1.Minimum = 0;
// If the offset does not make the Maximum less than zero, set its value.
if ((this.pictureBox1.Image.Size.Width - pictureBox1.ClientSize.Width) > 0)
{
this.hScrollBar1.Maximum =
this.pictureBox1.Image.Size.Width - pictureBox1.ClientSize.Width;
}
// If the VScrollBar is visible, adjust the Maximum of the
// HSCrollBar to account for the width of the VScrollBar.
if (this.vScrollBar1.Visible)
{
this.hScrollBar1.Maximum += this.vScrollBar1.Width;
}
this.hScrollBar1.LargeChange = this.hScrollBar1.Maximum / 10;
this.hScrollBar1.SmallChange = this.hScrollBar1.Maximum / 20;
// Adjust the Maximum value to make the raw Maximum value
// attainable by user interaction.
this.hScrollBar1.Maximum += this.hScrollBar1.LargeChange;
// If the offset does not make the Maximum less than zero, set its value.
if ((this.pictureBox1.Image.Size.Height - pictureBox1.ClientSize.Height) > 0)
{
this.vScrollBar1.Maximum =
this.pictureBox1.Image.Size.Height - pictureBox1.ClientSize.Height;
}
// If the HScrollBar is visible, adjust the Maximum of the
// VSCrollBar to account for the width of the HScrollBar.
if (this.hScrollBar1.Visible)
{
this.vScrollBar1.Maximum += this.hScrollBar1.Height;
}
this.vScrollBar1.LargeChange = this.vScrollBar1.Maximum / 10;
this.vScrollBar1.SmallChange = this.vScrollBar1.Maximum / 20;
// Adjust the Maximum value to make the raw Maximum value
// attainable by user interaction.
this.vScrollBar1.Maximum += this.vScrollBar1.LargeChange;
}
private void pictureBox1_Resize(object sender, EventArgs e)
{
// If the PictureBox has an image, see if it needs
// scrollbars and refresh the image.
if (pictureBox1.Image != null)
{
this.DisplayScrollBars();
this.SetScrollBarValues();
this.Refresh();
}
}
As I comment above, the correct way of doing this is to put your pictureBox in a panel and set the panel.AutoScroll=true. Also you need to set pictureBox.SizeMode=AutoSize so it is sized equal to the size of the image that it contains. Check: PictureBoxSizeMode Enumeration
I set the DrawMode to OwnerDrawText and tacked on to the DrawNode event, added my code to draw the text the way I want and all works well save for some odd black selection highlighting when a node is selected.
No problem, I added logic to check for if the node's state was highlighted and drew my own highlighting except the black highlighting gets added when a node is clicked, not just selected... The highlight gets drawn over by my rectangle once the mouse button is released but does get drawn and blinks...it's annoying. :/
Apparently I forgot to actually ask my question...How would one go about getting rid of the selection without completely handling the drawing?
In my experience you usually can't. Either you draw the item yourself or you don't. If you try to composite your graphics on top of those drawn by the control, you'll end up with glitches.
It is a bit of a pain because you have to handle focus rectangles, selection highlights, and drawing all the glyphs yourself.
On the plus side, Visual Styles can be used to do most of the work.
Here's some code that will get you most of the way there (it's incomplete, in that it uses some methods not included, and it doesn't render exactly what a normal treeview does because it supports grad filled items and columns, but should be a handy reference)
protected virtual void OnDrawTreeNode(object sender, DrawTreeNodeEventArgs e)
{
string text = e.Node.Text;
Rectangle itemRect = e.Bounds;
if (e.Bounds.Height < 1 || e.Bounds.Width < 1)
return;
int cIndentBy = 19; // TODO - support Indent value
int cMargin = 6; // TODO - this is a bit random, it's slaved off the Indent in some way
int cTwoMargins = cMargin * 2;
int indent = (e.Node.Level * cIndentBy) + cMargin;
int iconLeft = indent; // Where to draw parentage lines & icon/checkbox
int textLeft = iconLeft + 16; // Where to draw text
Color leftColour = e.Node.BackColor;
Color textColour = e.Node.ForeColor;
if (Bitfield.IsBitSet(e.State, TreeNodeStates.Grayed))
textColour = Color.FromArgb(255,128,128,128);
// Grad-fill the background
Brush backBrush = new SolidBrush(leftColour);
e.Graphics.FillRectangle(backBrush, itemRect);
// Faint underline along the bottom of each item
Color separatorColor = ColourUtils.Mix(leftColour, Color.FromArgb(255,0,0,0), 0.02);
Pen separatorPen = new Pen(separatorColor);
e.Graphics.DrawLine(separatorPen, itemRect.Left, itemRect.Bottom-1, itemRect.Right, itemRect.Bottom-1);
// Bodged to use Button styles as Treeview styles not available on my laptop...
if (!HideSelection)
{
if (Bitfield.IsBitSet(e.State, TreeNodeStates.Selected) || Bitfield.IsBitSet(e.State, TreeNodeStates.Hot))
{
Rectangle selRect = new Rectangle(textLeft, itemRect.Top, itemRect.Right - textLeft, itemRect.Height);
VisualStyleRenderer renderer = new VisualStyleRenderer((ContainsFocus) ? VisualStyleElement.Button.PushButton.Hot
: VisualStyleElement.Button.PushButton.Normal);
renderer.DrawBackground(e.Graphics, selRect);
// Bodge to make VisualStyle look like Explorer selections - overdraw with alpha'd white rectangle to fade the colour a lot
Brush bodge = new SolidBrush(Color.FromArgb((Bitfield.IsBitSet(e.State, TreeNodeStates.Hot)) ? 224 : 128,255,255,255));
e.Graphics.FillRectangle(bodge, selRect);
}
}
Pen dotPen = new Pen(Color.FromArgb(128,128,128));
dotPen.DashStyle = DashStyle.Dot;
int midY = (itemRect.Top + itemRect.Bottom) / 2;
// Draw parentage lines
if (ShowLines)
{
int x = cMargin * 2;
if (e.Node.Level == 0 && e.Node.PrevNode == null)
{
// The very first node in the tree has a half-height line
e.Graphics.DrawLine(dotPen, x, midY, x, itemRect.Bottom);
}
else
{
TreeNode testNode = e.Node; // Used to only draw lines to nodes with Next Siblings, as in normal TreeViews
for (int iLine = e.Node.Level; iLine >= 0; iLine--)
{
if (testNode.NextNode != null)
{
x = (iLine * cIndentBy) + (cMargin * 2);
e.Graphics.DrawLine(dotPen, x, itemRect.Top, x, itemRect.Bottom);
}
testNode = testNode.Parent;
}
x = (e.Node.Level * cIndentBy) + cTwoMargins;
e.Graphics.DrawLine(dotPen, x, itemRect.Top, x, midY);
}
e.Graphics.DrawLine(dotPen, iconLeft + cMargin, midY, iconLeft + cMargin + 10, midY);
}
// Draw Expand (plus/minus) icon if required
if (ShowPlusMinus && e.Node.Nodes.Count > 0)
{
// Use the VisualStyles renderer to use the proper OS-defined glyphs
Rectangle expandRect = new Rectangle(iconLeft-1, midY - 7, 16, 16);
VisualStyleElement element = (e.Node.IsExpanded) ? VisualStyleElement.TreeView.Glyph.Opened
: VisualStyleElement.TreeView.Glyph.Closed;
VisualStyleRenderer renderer = new VisualStyleRenderer(element);
renderer.DrawBackground(e.Graphics, expandRect);
}
// Draw the text, which is separated into columns by | characters
Point textStartPos = new Point(itemRect.Left + textLeft, itemRect.Top);
Point textPos = new Point(textStartPos.X, textStartPos.Y);
Font textFont = e.Node.NodeFont; // Get the font for the item, or failing that, for this control
if (textFont == null)
textFont = Font;
StringFormat drawFormat = new StringFormat();
drawFormat.Alignment = StringAlignment.Near;
drawFormat.LineAlignment = StringAlignment.Center;
drawFormat.FormatFlags = StringFormatFlags.NoWrap;
string [] columnTextList = text.Split('|');
for (int iCol = 0; iCol < columnTextList.GetLength(0); iCol++)
{
Rectangle textRect = new Rectangle(textPos.X, textPos.Y, itemRect.Right - textPos.X, itemRect.Bottom - textPos.Y);
if (mColumnImageList != null && mColumnImageList[iCol] != null)
{
// This column has an imagelist assigned, so we use the column text as an integer zero-based index
// into the imagelist to indicate the icon to display
int iImage = 0;
try
{
iImage = MathUtils.Clamp(Convert.ToInt32(columnTextList[iCol]), 0, mColumnImageList[iCol].Images.Count);
}
catch(Exception)
{
iImage = 0;
}
e.Graphics.DrawImageUnscaled(mColumnImageList[iCol].Images[iImage], textRect.Left, textRect.Top);
}
else
e.Graphics.DrawString(columnTextList[iCol], textFont, new SolidBrush(textColour), textRect, drawFormat);
textPos.X += mColumnWidthList[iCol];
}
// Draw Focussing box around the text
if (e.State == TreeNodeStates.Focused)
{
SizeF size = e.Graphics.MeasureString(text, textFont);
size.Width = (ClientRectangle.Width - 2) - textStartPos.X;
size.Height += 1;
Rectangle rect = new Rectangle(textStartPos, size.ToSize());
e.Graphics.DrawRectangle(dotPen, rect);
// ControlPaint.DrawFocusRectangle(e.Graphics, Rect);
}
}
I have a multiline text string (e.g. "Stuff\nMore Stuff\nYet More Stuff"), and I want to paint it, along with a bitmap into a tooltip. Since I am painting the bitmap, I need to set OwnerDraw to true, which I am doing. I am also handling the Popup event, so I can size the tooltip to be large enough to hold the text and the bitmap.
I am calling e.DrawBackground and e.DrawBorder(), and then painting my bitmap on the left side of the tooltip area.
Is there a set of flags I can pass to e.DrawText() in order to left-align the text, but to offset it so that it doesn't get painted over my bitmap? Or do I need to custom draw all the text as well (which will probably involve splitting the string on newlines, etc)?
UPDATED: The final code looks like this:
private void _ItemTip_Draw(object sender, DrawToolTipEventArgs e)
{
e.DrawBackground();
e.DrawBorder();
// Reserve a square of size e.Bounds.Height x e.Bounds.Height
// for the image. Keep a margin around it so that it looks good.
int margin = 2;
Image i = _ItemTip.Tag as Image;
if (i != null)
{
int side = e.Bounds.Height - 2 * margin;
e.Graphics.DrawImage(i, new Rectangle(margin, margin, side, side));
}
// Construct bounding rectangle for text (don't want to paint it over the image).
int textOffset = e.Bounds.Height + 2 * margin;
RectangleF rText = e.Bounds;
rText.Offset(textOffset, 0);
rText.Width -= textOffset;
e.Graphics.DrawString(e.ToolTipText, e.Font, Brushes.Black, rText);
}
I assume that if you define the bounding rectangle to draw in (calculating the image offset yourself) you could just:
RectangleF rect = new RectangleF(100,100,100,100);
e.Graphics.DrawString(myString, myFont, myBrush, rect);
to calculate the Height of an owner drawn string s given a certain width w, we use the following code:
double MeasureStringHeight (Graphics g, string s, Font f, int w) {
double result = 0;
int n = s.Length;
int i = 0;
while (i < n) {
StringBuilder line = new StringBuilder();
int iLineStart = i;
int iSpace = -1;
SizeF sLine = new SizeF(0, 0);
while ((i < n) && (sLine.Width <= w)) {
char ch = s[i];
if ((ch == ' ') || (ch == '-')) {
iSpace = i;
}
line.Append(ch);
sLine = g.MeasureString(line.ToString(), f);
i++;
}
if (sLine.Width > w) {
if (iSpace >= 0) {
i = iSpace + 1;
} else {
i--;
}
// Assert(w > largest ch in line)
}
result += sLine.Height;
}
return result;
}
Regards,
tamberg