/*
* Decorations - Images, text or other things that can be rendered onto an ObjectListView
*
* Author: Phillip Piper
* Date: 19/08/2009 10:56 PM
*
* Change log:
* 2011-04-04 JPP - Added ability to have a gradient background on BorderDecoration
* v2.4
* 2010-04-15 JPP - Tweaked LightBoxDecoration a little
* v2.3
* 2009-09-23 JPP - Added LeftColumn and RightColumn to RowBorderDecoration
* 2009-08-23 JPP - Added LightBoxDecoration
* 2009-08-19 JPP - Initial version. Separated from Overlays.cs
*
* To do:
*
* Copyright (C) 2009-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
*/
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Windows.Forms;
namespace BrightIdeasSoftware
{
///
/// A decoration is an overlay that draws itself in relation to a given row or cell.
/// Decorations scroll when the listview scrolls.
///
public interface IDecoration : IOverlay
{
///
/// Gets or sets the row that is to be decorated
///
OLVListItem ListItem { get; set; }
///
/// Gets or sets the subitem that is to be decorated
///
OLVListSubItem SubItem { get; set; }
}
///
/// An AbstractDecoration is a safe do-nothing implementation of the IDecoration interface
///
public class AbstractDecoration : IDecoration
{
#region IDecoration Members
///
/// Gets or sets the row that is to be decorated
///
public OLVListItem ListItem {
get { return listItem; }
set { listItem = value; }
}
private OLVListItem listItem;
///
/// Gets or sets the subitem that is to be decorated
///
public OLVListSubItem SubItem {
get { return subItem; }
set { subItem = value; }
}
private OLVListSubItem subItem;
#endregion
#region Public properties
///
/// Gets the bounds of the decorations row
///
public Rectangle RowBounds {
get {
if (this.ListItem == null)
return Rectangle.Empty;
else
return this.ListItem.Bounds;
}
}
///
/// Get the bounds of the decorations cell
///
public Rectangle CellBounds {
get {
if (this.ListItem == null || this.SubItem == null)
return Rectangle.Empty;
else
return this.ListItem.GetSubItemBounds(this.ListItem.SubItems.IndexOf(this.SubItem));
}
}
#endregion
#region IOverlay Members
///
/// Draw the decoration
///
///
///
///
public virtual void Draw(ObjectListView olv, Graphics g, Rectangle r) {
}
#endregion
}
///
/// This decoration draws a slight tint over a column of the
/// owning listview. If no column is explicitly set, the selected
/// column in the listview will be used.
/// The selected column is normally the sort column, but does not have to be.
///
public class TintedColumnDecoration : AbstractDecoration
{
#region Constructors
///
/// Create a TintedColumnDecoration
///
public TintedColumnDecoration() {
this.Tint = Color.FromArgb(15, Color.Blue);
}
///
/// Create a TintedColumnDecoration
///
///
public TintedColumnDecoration(OLVColumn column)
: this() {
this.ColumnToTint = column;
}
#endregion
#region Properties
///
/// Gets or sets the column that will be tinted
///
public OLVColumn ColumnToTint {
get { return this.columnToTint; }
set { this.columnToTint = value; }
}
private OLVColumn columnToTint;
///
/// Gets or sets the color that will be 'tinted' over the selected column
///
public Color Tint {
get { return this.tint; }
set {
if (this.tint == value)
return;
if (this.tintBrush != null) {
this.tintBrush.Dispose();
this.tintBrush = null;
}
this.tint = value;
this.tintBrush = new SolidBrush(this.tint);
}
}
private Color tint;
private SolidBrush tintBrush;
#endregion
#region IOverlay Members
///
/// Draw a slight colouring over our tinted column
///
///
/// This overlay only works when:
/// - the list is in Details view
/// - there is at least one row
/// - there is a selected column (or a specified tint column)
///
///
///
///
public override void Draw(ObjectListView olv, Graphics g, Rectangle r) {
if (olv.View != System.Windows.Forms.View.Details)
return;
if (olv.GetItemCount() == 0)
return;
OLVColumn column = this.ColumnToTint ?? olv.SelectedColumn;
if (column == null)
return;
Point sides = NativeMethods.GetScrolledColumnSides(olv, column.Index);
if (sides.X == -1)
return;
Rectangle columnBounds = new Rectangle(sides.X, r.Top, sides.Y - sides.X, r.Bottom);
// Find the bottom of the last item. The tinting should extend only to there.
OLVListItem lastItem = olv.GetLastItemInDisplayOrder();
if (lastItem != null) {
Rectangle lastItemBounds = lastItem.Bounds;
if (!lastItemBounds.IsEmpty && lastItemBounds.Bottom < columnBounds.Bottom)
columnBounds.Height = lastItemBounds.Bottom - columnBounds.Top;
}
g.FillRectangle(this.tintBrush, columnBounds);
}
#endregion
}
///
/// This decoration draws an optionally filled border around a rectangle.
/// Subclasses must override CalculateBounds().
///
public class BorderDecoration : AbstractDecoration
{
#region Constructors
///
/// Create a BorderDecoration
///
public BorderDecoration()
: this(new Pen(Color.FromArgb(64, Color.Blue), 1)) {
}
///
/// Create a BorderDecoration
///
/// The pen used to draw the border
public BorderDecoration(Pen borderPen) {
this.BorderPen = borderPen;
}
///
/// Create a BorderDecoration
///
/// The pen used to draw the border
/// The brush used to fill the rectangle
public BorderDecoration(Pen borderPen, Brush fill) {
this.BorderPen = borderPen;
this.FillBrush = fill;
}
#endregion
#region Properties
///
/// Gets or sets the pen that will be used to draw the border
///
public Pen BorderPen {
get { return this.borderPen; }
set { this.borderPen = value; }
}
private Pen borderPen;
///
/// Gets or sets the padding that will be added to the bounds of the item
/// before drawing the border and fill.
///
public Size BoundsPadding {
get { return this.boundsPadding; }
set { this.boundsPadding = value; }
}
private Size boundsPadding = new Size(-1, 2);
///
/// How rounded should the corners of the border be? 0 means no rounding.
///
/// If this value is too large, the edges of the border will appear odd.
public float CornerRounding {
get { return this.cornerRounding; }
set { this.cornerRounding = value; }
}
private float cornerRounding = 16.0f;
///
/// Gets or sets the brush that will be used to fill the border
///
/// This value is ignored when using gradient brush
public Brush FillBrush {
get { return this.fillBrush; }
set { this.fillBrush = value; }
}
private Brush fillBrush = new SolidBrush(Color.FromArgb(64, Color.Blue));
///
/// Gets or sets the color that will be used as the start of a gradient fill.
///
/// This and FillGradientTo must be given value to show a gradient
public Color? FillGradientFrom {
get { return this.fillGradientFrom; }
set { this.fillGradientFrom = value; }
}
private Color? fillGradientFrom;
///
/// Gets or sets the color that will be used as the end of a gradient fill.
///
/// This and FillGradientFrom must be given value to show a gradient
public Color? FillGradientTo {
get { return this.fillGradientTo; }
set { this.fillGradientTo = value; }
}
private Color? fillGradientTo;
///
/// Gets or sets the fill mode that will be used for the gradient.
///
public LinearGradientMode FillGradientMode {
get { return this.fillGradientMode; }
set { this.fillGradientMode = value; }
}
private LinearGradientMode fillGradientMode = LinearGradientMode.Vertical;
#endregion
#region IOverlay Members
///
/// Draw a filled border
///
///
///
///
public override void Draw(ObjectListView olv, Graphics g, Rectangle r) {
Rectangle bounds = this.CalculateBounds();
if (!bounds.IsEmpty)
this.DrawFilledBorder(g, bounds);
}
#endregion
#region Subclass responsibility
///
/// Subclasses should override this to say where the border should be drawn
///
///
protected virtual Rectangle CalculateBounds() {
return Rectangle.Empty;
}
#endregion
#region Implementation utlities
///
/// Do the actual work of drawing the filled border
///
///
///
protected void DrawFilledBorder(Graphics g, Rectangle bounds) {
bounds.Inflate(this.BoundsPadding);
GraphicsPath path = this.GetRoundedRect(bounds, this.CornerRounding);
if (this.FillGradientFrom != null && this.FillGradientTo != null) {
if (this.FillBrush != null)
this.FillBrush.Dispose();
this.FillBrush = new LinearGradientBrush(bounds, this.FillGradientFrom.Value, this.FillGradientTo.Value, this.FillGradientMode);
}
if (this.FillBrush != null)
g.FillPath(this.FillBrush, path);
if (this.BorderPen != null)
g.DrawPath(this.BorderPen, path);
}
///
/// Create a GraphicsPath that represents a round cornered rectangle.
///
///
/// If this is 0 or less, the rectangle will not be rounded.
///
protected GraphicsPath GetRoundedRect(RectangleF rect, float diameter) {
GraphicsPath path = new GraphicsPath();
if (diameter <= 0.0f) {
path.AddRectangle(rect);
} else {
RectangleF arc = new RectangleF(rect.X, rect.Y, diameter, diameter);
path.AddArc(arc, 180, 90);
arc.X = rect.Right - diameter;
path.AddArc(arc, 270, 90);
arc.Y = rect.Bottom - diameter;
path.AddArc(arc, 0, 90);
arc.X = rect.Left;
path.AddArc(arc, 90, 90);
path.CloseFigure();
}
return path;
}
#endregion
}
///
/// Instances of this class draw a border around the decorated row
///
public class RowBorderDecoration : BorderDecoration
{
///
/// Gets or sets the index of the left most column to be used for the border
///
public int LeftColumn {
get { return leftColumn; }
set { leftColumn = value; }
}
private int leftColumn = -1;
///
/// Gets or sets the index of the right most column to be used for the border
///
public int RightColumn {
get { return rightColumn; }
set { rightColumn = value; }
}
private int rightColumn = -1;
///
/// Calculate the boundaries of the border
///
///
protected override Rectangle CalculateBounds() {
Rectangle bounds = this.RowBounds;
if (this.ListItem == null)
return bounds;
if (this.LeftColumn >= 0) {
Rectangle leftCellBounds = this.ListItem.GetSubItemBounds(this.LeftColumn);
if (!leftCellBounds.IsEmpty) {
bounds.Width = bounds.Right - leftCellBounds.Left;
bounds.X = leftCellBounds.Left;
}
}
if (this.RightColumn >= 0) {
Rectangle rightCellBounds = this.ListItem.GetSubItemBounds(this.RightColumn);
if (!rightCellBounds.IsEmpty) {
bounds.Width = rightCellBounds.Right - bounds.Left;
}
}
return bounds;
}
}
///
/// Instances of this class draw a border around the decorated subitem.
///
public class CellBorderDecoration : BorderDecoration
{
///
/// Calculate the boundaries of the border
///
///
protected override Rectangle CalculateBounds() {
return this.CellBounds;
}
}
///
/// This decoration puts a border around the cell being edited and
/// optionally "lightboxes" the cell (makes the rest of the control dark).
///
public class EditingCellBorderDecoration : BorderDecoration
{
#region Life and death
///
/// Create a EditingCellBorderDecoration
///
public EditingCellBorderDecoration() {
this.FillBrush = null;
this.BorderPen = new Pen(Color.DarkBlue, 2);
this.CornerRounding = 8;
this.BoundsPadding = new Size(10, 8);
}
///
/// Create a EditingCellBorderDecoration
///
/// Should the decoration use a lighbox display style?
public EditingCellBorderDecoration(bool useLightBox) : this()
{
this.UseLightbox = useLightbox;
}
#endregion
#region Configuration properties
///
/// Gets or set whether the decoration should make the rest of
/// the control dark when a cell is being edited
///
/// If this is true, FillBrush is used to overpaint
/// the control.
public bool UseLightbox {
get { return this.useLightbox; }
set {
if (this.useLightbox == value)
return;
this.useLightbox = value;
if (this.useLightbox) {
if (this.FillBrush == null)
this.FillBrush = new SolidBrush(Color.FromArgb(64, Color.Black));
}
}
}
private bool useLightbox;
#endregion
#region Implementation
///
/// Draw the decoration
///
///
///
///
public override void Draw(ObjectListView olv, Graphics g, Rectangle r) {
if (!olv.IsCellEditing)
return;
Rectangle bounds = olv.CellEditor.Bounds;
if (bounds.IsEmpty)
return;
bounds.Inflate(this.BoundsPadding);
GraphicsPath path = this.GetRoundedRect(bounds, this.CornerRounding);
if (this.FillBrush != null) {
if (this.UseLightbox) {
using (Region newClip = new Region(r)) {
newClip.Exclude(path);
Region originalClip = g.Clip;
g.Clip = newClip;
g.FillRectangle(this.FillBrush, r);
g.Clip = originalClip;
}
} else {
g.FillPath(this.FillBrush, path);
}
}
if (this.BorderPen != null)
g.DrawPath(this.BorderPen, path);
}
#endregion
}
///
/// This decoration causes everything *except* the row under the mouse to be overpainted
/// with a tint, making the row under the mouse stand out in comparison.
/// The darker and more opaque the fill color, the more obvious the
/// decorated row becomes.
///
public class LightBoxDecoration : BorderDecoration
{
///
/// Create a LightBoxDecoration
///
public LightBoxDecoration() {
this.BoundsPadding = new Size(-1, 4);
this.CornerRounding = 8.0f;
this.FillBrush = new SolidBrush(Color.FromArgb(72, Color.Black));
}
///
/// Draw a tint over everything in the ObjectListView except the
/// row under the mouse.
///
///
///
///
public override void Draw(ObjectListView olv, Graphics g, Rectangle r) {
if (!r.Contains(olv.PointToClient(Cursor.Position)))
return;
Rectangle bounds = this.RowBounds;
if (bounds.IsEmpty) {
if (olv.View == View.Tile)
g.FillRectangle(this.FillBrush, r);
return;
}
using (Region newClip = new Region(r)) {
bounds.Inflate(this.BoundsPadding);
newClip.Exclude(this.GetRoundedRect(bounds, this.CornerRounding));
Region originalClip = g.Clip;
g.Clip = newClip;
g.FillRectangle(this.FillBrush, r);
g.Clip = originalClip;
}
}
}
///
/// Instances of this class put an Image over the row/cell that it is decorating
///
public class ImageDecoration : ImageAdornment, IDecoration
{
#region Constructors
///
/// Create an image decoration
///
public ImageDecoration() {
this.Alignment = ContentAlignment.MiddleRight;
}
///
/// Create an image decoration
///
///
public ImageDecoration(Image image)
: this() {
this.Image = image;
}
///
/// Create an image decoration
///
///
///
public ImageDecoration(Image image, int transparency)
: this() {
this.Image = image;
this.Transparency = transparency;
}
///
/// Create an image decoration
///
///
///
public ImageDecoration(Image image, ContentAlignment alignment)
: this() {
this.Image = image;
this.Alignment = alignment;
}
///
/// Create an image decoration
///
///
///
///
public ImageDecoration(Image image, int transparency, ContentAlignment alignment)
: this() {
this.Image = image;
this.Transparency = transparency;
this.Alignment = alignment;
}
#endregion
#region IDecoration Members
///
/// Gets or sets the item being decorated
///
public OLVListItem ListItem {
get { return listItem; }
set { listItem = value; }
}
private OLVListItem listItem;
///
/// Gets or sets the sub item being decorated
///
public OLVListSubItem SubItem {
get { return subItem; }
set { subItem = value; }
}
private OLVListSubItem subItem;
#endregion
#region Commands
///
/// Draw this decoration
///
/// The ObjectListView being decorated
/// The Graphics used for drawing
/// The bounds of the rendering
public virtual void Draw(ObjectListView olv, Graphics g, Rectangle r) {
this.DrawImage(g, this.CalculateItemBounds(this.ListItem, this.SubItem));
}
#endregion
}
///
/// Instances of this class draw some text over the row/cell that they are decorating
///
public class TextDecoration : TextAdornment, IDecoration
{
#region Constructors
///
/// Create a TextDecoration
///
public TextDecoration() {
this.Alignment = ContentAlignment.MiddleRight;
}
///
/// Create a TextDecoration
///
///
public TextDecoration(string text)
: this() {
this.Text = text;
}
///
/// Create a TextDecoration
///
///
///
public TextDecoration(string text, int transparency)
: this() {
this.Text = text;
this.Transparency = transparency;
}
///
/// Create a TextDecoration
///
///
///
public TextDecoration(string text, ContentAlignment alignment)
: this() {
this.Text = text;
this.Alignment = alignment;
}
///
/// Create a TextDecoration
///
///
///
///
public TextDecoration(string text, int transparency, ContentAlignment alignment)
: this() {
this.Text = text;
this.Transparency = transparency;
this.Alignment = alignment;
}
#endregion
#region IDecoration Members
///
/// Gets or sets the item being decorated
///
public OLVListItem ListItem {
get { return listItem; }
set { listItem = value; }
}
private OLVListItem listItem;
///
/// Gets or sets the sub item being decorated
///
public OLVListSubItem SubItem {
get { return subItem; }
set { subItem = value; }
}
private OLVListSubItem subItem;
#endregion
#region Commands
///
/// Draw this decoration
///
/// The ObjectListView being decorated
/// The Graphics used for drawing
/// The bounds of the rendering
public virtual void Draw(ObjectListView olv, Graphics g, Rectangle r) {
this.DrawText(g, this.CalculateItemBounds(this.ListItem, this.SubItem));
}
#endregion
}
}