How to custom draw a cell in SourceGrid - c#

I am using SourceGrid (https://www.nuget.org/packages/SourceGrid) in my WinForms app and I want to custom draw a cell. I have found a way to do it, I just feel there must be a better, less hacky way. Here is what I do:
When creating a new cell:
Cell c = new Cell(Value);
var v = new SourceGrid.Cells.Views.Cell();
v.ElementText = new MyCustomCellElement(); // this will draw for me
c.View = v;
Later when filling the cells with data:
((Grid[row][col].View as SourceGrid.Cells.Views.Cell).ElementText as MyCustomCellElement).MyCustomData = MyCustomObj;
And the code that does the custom draw:
class MyCustomCellElement : DevAge.Drawing.VisualElements.TextGDI
{
public MyObject MyCustomData { get; set; }
protected override void OnDraw(GraphicsCache graphics, RectangleF area)
{
if (Value == null || Value.Length == 0)
return;
SolidBrush brush;
if (Enabled)
brush = graphics.BrushsCache.GetBrush(ForeColor);
else
brush = graphics.BrushsCache.GetBrush(Color.FromKnownColor(KnownColor.GrayText));
// do your draw here, this is the default:
graphics.Graphics.DrawString(Value, Font, brush, area, StringFormat);
}
}
The double cast when putting data in the cell doesn't smell right - it is a hack. It works but I am sure there is a 'proper' way to do it.

Related

How to make DrawingVisual objects in C# have unique Pen Brush colour, but share the same thickness

I'm building an application that can load in Vector files and display them on a Canvas. To zoom and pan efficiently I'm using DrawingVisual shapes for my background maps that don't change often.
In order to keep the line thickness the same regardless of zoom, I modify the Pen.Thickness with each zoom event.
This is working fine, but now that I'm trying to assign colours to the Pen I'm running into problems. Because all objects share the same Pen for thickness/zooming reasons, I don't know how to assign different colours to different objects.
By sharing the same Pen every object is forced to be the same colour.
How can I ensure that all the shapes receive Thickness updates after a zoom event, but have unique colours per object? If I need a different Pen for every colour, there is no way of know how many pens I would need.
My code is below:
Pen myPen = new Pen();
This is my Method that produces polylines:
private DrawingVisual CreatePolyline(PointCollection points, Brush colour)
{
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
myPen.Thickness = 0.5 / Coords.zoom;
myPen.Brush = colour;
var geometry = new StreamGeometry();
using (StreamGeometryContext ctx = geometry.Open())
{
ctx.BeginFigure(points[0], false, false);
List<Point> _points = points.Skip(1).ToList();
ctx.PolyLineTo(_points, true, false);
}
geometry.Freeze();
drawingContext.DrawGeometry(null, myPen, geometry);
drawingContext.Close();
return drawingVisual;
}
This Method is triggered every time I zoom
public void Handle(ZoomEvent zoomEvent)
{
myPen.Thickness = 0.5 / zoomEvent.Zoom;
}
This is the method that assigns colour based on what file it belongs in, layer, item itself and any overriding user choices.
private void UpdateVisualColection()
{
visualCollection.Clear();
foreach (MapReference reference in backgroundMaps.References)
{
if (reference.IsVisible == true)
{
foreach (ReferenceLayer layer in reference.Layers)
{
if (layer.IsVisible == true)
{
foreach (SingleItem item in layer.SingleItems)
{
System.Windows.Media.Color colour;
if (reference.ColourOveride == true)
{
colour = reference.Colour;
}
else if (item.ColourByLayer == true || layer.ColourOveride == true)
{
colour = layer.Colour;
}
else
{
colour = item.ColourOveride;
}
var Brush = new SolidColorBrush(Color.FromArgb(255, colour.R, colour.G, colour.B));
visualCollection.Add(CreatePolyline(item.Points, Brush));
}
}
}
}
}
}

Is it possible to create a non-shaped control?

I'm trying to place an image that has no definitive shape (a hat for example) on top of a different image control.
The thing is, since the control has a definitive shape, it leaves the default background color to cover up the space left blank. The image control is the exact same size of the image.
I tried using control.BackColor = Color.Transparent; but it doesn't seem to work.
Any other suggestions?
You can use Control.Region for this purpose
GraphicsPath path = new GraphicsPath();
path.AddEllipse(control.ClientRectangle);
control.Region = new Region(path);
try this, you can create any shape using GraphicsPath and set it to Region for instance I created ellipse.
Edit
If you just want to set BackColor = Color.Transparent. for some reason some controls doesn't allow this. in such cases you can do the following
public class CustomControl1 : Control
{
public CustomControl1()
{
this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
}
}
Create a descendant of your control and set this.SetStyle(ControlStyles.SupportsTransparentBackColor, true); that should do the trick
If your Image Control (like a PictureBox) is not moved (by holding mouse down and dragging) by user at runtime, you can use this technique which allows you to display images on top of each other. The images should have transparent background:
public class ImageControl : Control {
public ImageControl(){
SetStyle(ControlStyles.Opaque, true);
}
public Image Image {get;set;}
protected override CreateParams CreateParams {
get {
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x20;
return cp;
}
}
protected override void OnPaint(PaintEventArgs e){
if(Image != null) e.Graphics.DrawImage(Image, Point.Empty);
}
}
You can use the control above instead of a PictureBox. Moving this control by dragging at runtime causes flicker much. So if you want so I think there is only 1 solution which uses Region. In this approach, you have to turn your Bitmap to a Region and assign this Region for your Control.Region property. The link given by Chris Dunaway is very helpful for you to do this. However I have to say that the Region has not a smooth border as you may expect. That's a shortage of this approach. For your convenience, I'll post the code with a little modification here, this code uses LockBits which will outperform the original code:
public class Util {
//invert will toggle backColor to foreColor (in fact, I mean foreColor here is the Solid Color which makes your image distinct from the background).
public static Region RegionFromBitmap(Bitmap bm, Color backColor, bool invert)
{
Region rgn = new Region();
rgn.MakeEmpty();//This is very important
int argbBack = backColor.ToArgb();
BitmapData data = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
int[] bits = new int[bm.Width * bm.Height];
Marshal.Copy(data.Scan0, bits, 0, bits.Length);
//
Rectangle line = Rectangle.Empty;
line.Height = 1;
bool inImage = false;
for (int i = 0; i < bm.Height; i++)
{
for (int j = 0; j < bm.Width; j++)
{
int c = bits[j + i * bm.Width];
if (!inImage)
{
if (invert ? c == argbBack : c != argbBack)
{
inImage = true;
line.X = j;
line.Y = i;
}
}
else if(invert ? c != argbBack : c == argbBack)
{
inImage = false;
line.Width = j - line.X;
rgn.Union(line);
}
}
}
bm.UnlockBits(data);
return rgn;
}
}
//Use the code
//if your Bitmap is a PNG with transparent background, you can get the Region from it like this:
Region rgn = Util.RegionFromBitmap(yourPng, Color.FromArgb(0), false);
//if your Bitmap has a figure with solid color of Black, you can get the Region like this:
Region rgn = Util.RegionFromBitmap(yourPng, Color.Black, true);

How to dynamically change / set checkedListBox item fore colour

I have code below. How can i set checkedListBox item fore colour depending on if item is checked or not checked?
private void FindSelectedUserRoles()
{
lblSelectedUser.Text = Code.CommonUtilities.getDgvStringColValue(dataGridViewUserList, "UserName").Trim();
//iterate all roles selected user is member of
for (int i = 0; i < checkedListRoles.Items.Count; i++)
{
string roleName = checkedListRoles.Items[i].ToString();
string selectedUserRoles = Code.MemberShipManager.GetSpecificUsersRoles(lblSelectedUser.Text.Trim());
if (selectedUserRoles.Contains(roleName))
{
checkedListRoles.SetItemChecked(i, true);
//here i want to set item fore colour to green
}
else if (selectedUserRoles.Contains(roleName) == false)
{
checkedListRoles.SetItemChecked(i, false);
//and here, i want item fore colour to remain black
}
}
}
Since it's rather complicated to draw the thing yourself, you could actually let the original control draw itself -- just tweaking the color. This is my suggestion:
public class CustomCheckedListBox : CheckedListBox
{
protected override void OnDrawItem(DrawItemEventArgs e)
{
Color foreColor;
if (e.Index >= 0)
{
foreColor = GetItemChecked(e.Index) ? Color.Green : Color.Red;
}
else
{
foreColor = e.ForeColor;
}
// Copy the original event args, just tweaking the fore color.
var tweakedEventArgs = new DrawItemEventArgs(
e.Graphics,
e.Font,
e.Bounds,
e.Index,
e.State,
foreColor,
e.BackColor);
// Call the original OnDrawItem, but supply the tweaked color.
base.OnDrawItem(tweakedEventArgs);
}
}
I think you have to draw your own CheckedListBox item like this:
public class CustomCheckedListBox : CheckedListBox
{
public CustomCheckedListBox()
{
DoubleBuffered = true;
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
Size checkSize = CheckBoxRenderer.GetGlyphSize(e.Graphics, System.Windows.Forms.VisualStyles.CheckBoxState.MixedNormal);
int dx = (e.Bounds.Height - checkSize.Width)/2;
e.DrawBackground();
bool isChecked = GetItemChecked(e.Index);//For some reason e.State doesn't work so we have to do this instead.
CheckBoxRenderer.DrawCheckBox(e.Graphics, new Point(dx, e.Bounds.Top + dx), isChecked ? System.Windows.Forms.VisualStyles.CheckBoxState.CheckedNormal : System.Windows.Forms.VisualStyles.CheckBoxState.UncheckedNormal);
using (StringFormat sf = new StringFormat { LineAlignment = StringAlignment.Center })
{
using (Brush brush = new SolidBrush(isChecked ? CheckedItemColor : ForeColor))
{
e.Graphics.DrawString(Items[e.Index].ToString(), Font, brush, new Rectangle(e.Bounds.Height, e.Bounds.Top, e.Bounds.Width - e.Bounds.Height, e.Bounds.Height), sf);
}
}
}
Color checkedItemColor = Color.Green;
public Color CheckedItemColor
{
get { return checkedItemColor; }
set
{
checkedItemColor = value;
Invalidate();
}
}
}
If you want to set CheckedColor differently for each item, you have to store the CheckedColor setting for each item (such as in a Collection) and reference the CheckedColor using Index. However I think it's a little much work to do. So if you have such a requirement, going for ListView instead would be better.
I think you should try ListView instead of checkedListBox. It has necessary properties and could be customized as you wish. Just set Checkboxes property to true, and then in your code add forecolor like that:
listView1.Items[i].ForeColor = Color.Red;
Expanding on #Mattias' answer, I made this custom control to fit my needs. I needed it to have colors depending on other factors than the Checked value.
public class CheckedListBoxColorable : CheckedListBox
{
/// <summary>
/// Controls the forecolors of the objects in the Items collection.
/// If the item is not represented, it will have the default forecolor.
/// </summary>
public Dictionary<object, Color> Colors { get; set; }
public CheckedListBoxColorable()
{
this.DoubleBuffered = true; //prevent flicker, not sure if this is necessary.
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
//Default forecolor
Color foreColor = e.ForeColor;
//Item to be drawn
object item = null;
if (e.Index >= 0) //If index is -1, no customization is necessary
{
//Find the item to be drawn
if (this.Items.Count > e.Index) item = this.Items[e.Index];
//If the item was found and we have a color for it, get the custom forecolor
if (item != null && this.Colors != null && this.Colors.ContainsKey(item))
{
foreColor = this.Colors[item];
}
}
// Copy the original event args, just tweaking the forecolor.
var tweakedEventArgs = new DrawItemEventArgs(
e.Graphics,
e.Font,
e.Bounds,
e.Index,
e.State,
foreColor,
e.BackColor);
// Call the original OnDrawItem, but supply the tweaked color.
base.OnDrawItem(tweakedEventArgs);
}
}
Usage:
//Set the colors I want for my objects
foreach (var obj in objects)
{
//Add your own logic here to set the color depending on whatever criteria you have
if (obj.SomeProperty) lstBoxes.Colors.Add(obj, Color.Green);
else lstBoxes.Colors.Add(obj, Color.Red);
}
//Add the items to the checkedlistbox
lstBoxes.Items.AddRange(objects.ToArray());
The accepted answer worked for me but it needs modifying if you want to disable the CustomCheckedListBox.
I modified the code as follows: -
I changed the 'CheckBoxRenderer.DrawCheckBox...' line to
if(Enabled)
{
CheckBoxRenderer.DrawCheckBox(e.Graphics, new Point(dx, e.Bounds.Top + dx), isChecked ? System.Windows.Forms.VisualStyles.CheckBoxState.CheckedNormal : System.Windows.Forms.VisualStyles.CheckBoxState.UncheckedNormal);
}
else
{
CheckBoxRenderer.DrawCheckBox(e.Graphics, new Point(dx, e.Bounds.Top + dx), isChecked ? System.Windows.Forms.VisualStyles.CheckBoxState.CheckedDisabled : System.Windows.Forms.VisualStyles.CheckBoxState.UncheckedDisabled);
}
and then I changed the 'using (Brush brush = new SolidBrush...' line to
using (Brush brush = new SolidBrush(isChecked ? CheckedItemColor : (Enabled ? ForeColor : SystemColors.GrayText)))
This caused enabling/disabling to work for me.

How to make Glass Button ignore form's gradient?

There's some really great code for a glass button here: http://www.lukesw.net/articles/GlassButton.aspx
The only trouble I have with this button is that if I apply a gradient to my forms it affects the color of the button so that it's not quite what I chose at design time. I don't know whether it's the code I'm using to apply the form gradient that's causing this or if the button is not totally opaque or what. I tried fooling around with the button code a bit but didn't get anywhere. You can get the code for the button at the link I posted above. Below is the code I'm using for my form gradient which is located in the form itself right now:
private Color _Color1 = Color.Gainsboro;
private Color _Color2 = Color.Blue;
private float _ColorAngle = 60f;
public Color Color1
{
get { return _Color1; }
set {
_Color1 = value;
this.Invalidate(); // Tell the Form to repaint itself
}
}
public Color Color2
{
get { return _Color2; }
set {
_Color2 = value;
this.Invalidate(); // Tell the Form to repaint itself
}
}
public float ColorAngle
{
get { return _ColorAngle; }
set {
_ColorAngle = value;
this.Invalidate(); // Tell the Form to repaint itself
}
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
Graphics g = pevent.Graphics;
Rectangle rBackground = new Rectangle(0, 0, this.Width, this.Height);
System.Drawing.Drawing2D.LinearGradientBrush bBackground
= new System.Drawing.Drawing2D.LinearGradientBrush(rBackground,
_Color1, _Color2, _ColorAngle);
g.FillRectangle(bBackground, rBackground);
bBackground.Dispose();
}
Any pointers on how I can get this button to display the same at runtime as it does at design time would be greatly appreciated!
In the DrawButtonBackground method in GlowButton.cs, just change the opacity to fully opaque (255):
#region " content "
using (GraphicsPath bb = CreateRoundRectangle(rect, 2))
{
//int opacity = pressed ? 0xcc : 0x7f;
int opacity = 255;
using (Brush br = new SolidBrush(Color.FromArgb(opacity, backColor)))
{
g.FillPath(br, bb);
}
}
#endregion

Trouble with TreeView.DrawNode - OwnerDrawText

I have an app that is connected to a remote server and polling data when needed. It has a TreeView where the Nodes represent the objects that are available and the color of the text indicate whether the data has been loaded or not; gray-italicized indicates not loaded, black, regular text is loaded.
Currently I have set the TreeView to be OwnderDrawText and have the TreeView.DrawNode function simply draw the text as so:
private void TreeViewDrawNode(object sender, DrawTreeNodeEventArgs e)
{
if (!e.Node.IsVisible)
{
return;
}
bool bLoaded = false;
if (e.Bounds.Location.X >= 0 && e.Bounds.Location.Y >= 0)
{
if(e.Node.Tag != null)
{
//...
// code determining whether data has been loaded is done here
// setting bLoaded true or false
//...
}
else
{
e.DrawDefault = true;
return;
}
Font useFont = null;
Brush useBrush = null;
if (bLoaded)
{
useFont = e.Node.TreeView.Font;
useBrush = SystemBrushes.WindowText;
}
else
{
useFont = m_grayItallicFont;
useBrush = SystemBrushes.GrayText;
}
e.Graphics.DrawString(e.Node.Text, useFont, useBrush, e.Bounds.Location);
}
}
I figured that would be enough, however, this has been causing some issues;
When a node is selected, focused or not, it doesn't envelop all of the text, example (I hope imgur is ok).
When the node is focused, the dotted outline doesn't show either. If you compare it with this example. The nodes with the "log" in the text are using the e.DefaultDraw = true
I tried following the example given in this question. It looked something like this:
private void TreeViewDrawNode(object sender, DrawTreeNodeEventArgs e)
{
if (!e.Node.IsVisible)
{
return;
}
bool bLoaded = false;
if (e.Bounds.Location.X >= 0 && e.Bounds.Location.Y >= 0)
{
if(e.Node.Tag != null)
{
//...
// code determining whether data has been loaded is done here
// setting bLoaded true or false
//...
}
else
{
e.DrawDefault = true;
return;
}
//Select the font and brush depending on whether the property has been loaded
Font useFont = null;
Brush useBrush = null;
if (bLoaded)
{
useFont = e.Node.TreeView.Font;
useBrush = SystemBrushes.WindowText;
}
else
{
//member variable defined elsewhere
useFont = m_grayItallicFont;
useBrush = SystemBrushes.GrayText;
}
//Begin drawing of the text
//Get the rectangle that will be used to draw
Rectangle itemRect = e.Bounds;
//Move the rectangle over by 1 so it isn't on top of the check box
itemRect.X += 1;
//Figure out the text position
Point textStartPos = new Point(itemRect.Left, itemRect.Top);
Point textPos = new Point(textStartPos.X, textStartPos.Y);
//generate the text rectangle
Rectangle textRect = new Rectangle(textPos.X, textPos.Y, itemRect.Right - textPos.X, itemRect.Bottom - textPos.Y);
int textHeight = (int)e.Graphics.MeasureString(e.Node.Text, useFont).Height;
int textWidth = (int)e.Graphics.MeasureString(e.Node.Text, useFont).Width;
textRect.Height = textHeight;
//Draw the highlighted box
if ((e.State & TreeNodeStates.Selected) != 0)
{
//e.Graphics.FillRectangle(SystemBrushes.Highlight, textRect);
//use pink to see the difference
e.Graphics.FillRectangle(Brushes.Pink, textRect);
}
//widen the rectangle by 3 pixels, otherwise all of the text won't fit
textRect.Width = textWidth + 3;
//actually draw the text
e.Graphics.DrawString(e.Node.Text, useFont, useBrush, e.Bounds.Location);
//Draw the box around the focused node
if ((e.State & TreeNodeStates.Focused) != 0)
{
textRect.Width = textWidth;
Pen focusPen = new Pen(Color.Black);
focusPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
e.Graphics.DrawRectangle(focusPen, textRect);
}
}
}
However, the results were this. (Note, used pink to differentiate the colors). As you can see, the highlighted background doesn't extend all the way to where the focused dotted line is at. And there's also another box that is drawn as well.
I'm slightly stumped on how to fix this. All I want is to have gray italicized text when something is loaded. The first and simplest approach doesn't quite work and the second method feels like I'm doing way too much.
After all that, does anyone have any suggestions on how to do this properly, because there's got to be a simpler way.
Thank you in advance.
You'll need to use TextRenderer.DrawText(). That's what TreeView uses, it renders text slightly different from Graphics.DrawString().

Categories

Resources