WinForms自定义3按钮单元格

huangapple go评论53阅读模式
英文:

WinForms Custom 3 Button Cell

问题

我正在尝试创建一个自定义的DataGridViewCell,其中包含3个水平可点击的按钮。我已经在代码中走得很远,如下所示,但我需要一种方法来在单元格中显示这3个按钮。到目前为止,我只能绘制文本。我甚至尝试声明一个Panel对象,以便更容易操作这些按钮。

public partial class CustomButtonCell : DataGridViewButtonCell
{
    private Panel buttonPanel;
    private Button editButton;
    private Button deleteButton;
    private Button approveButton;
    private Button cancelButton;
    public bool Enabled { get; set; }

    public CustomButtonCell()
    {
        this.buttonPanel = new Panel();
        this.editButton = new Button();
        this.deleteButton = new Button();
        this.approveButton = new Button();
        this.cancelButton = new Button();

        this.editButton.Text = "Edit";
        this.deleteButton.Text = "Delete";
        this.approveButton.Text = "Approve";
        this.cancelButton.Text = "Cancel";

        this.buttonPanel.Controls.Add(this.editButton);
        this.buttonPanel.Controls.Add(this.deleteButton);
        this.buttonPanel.Controls.Add(this.approveButton);
        this.buttonPanel.Controls.Add(this.cancelButton);
        this.Enabled = true;
    }

    // 覆盖Clone方法,以便复制Enabled属性。
    public override object Clone()
    {
        CustomButtonCell cell = (CustomButtonCell)base.Clone();
        cell.Enabled = this.Enabled;
        return cell;
    }

    protected override void Paint(Graphics graphics,
           Rectangle clipBounds, Rectangle cellBounds, int rowIndex,
           DataGridViewElementStates elementState, object value,
           object formattedValue, string errorText,
           DataGridViewCellStyle cellStyle,
           DataGridViewAdvancedBorderStyle advancedBorderStyle,
           DataGridViewPaintParts paintParts)
    {
        // 调用基类方法来绘制默认单元格外观。
        base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState,
            value, formattedValue, errorText, cellStyle,
            advancedBorderStyle, paintParts);

        // 计算绘制按钮的区域。
        Rectangle buttonArea1 = cellBounds;
        Rectangle buttonAdjustment = this.BorderWidths(advancedBorderStyle);
        buttonArea1.X += buttonAdjustment.X;
        buttonArea1.Y += buttonAdjustment.Y;
        buttonArea1.Height -= buttonAdjustment.Height;
        buttonArea1.Width -= buttonAdjustment.Width;
        Rectangle buttonArea2 = cellBounds;
        Rectangle buttonAdjustment2 = this.BorderWidths(advancedBorderStyle);
        buttonArea2.X += buttonAdjustment2.X + buttonArea1.Width;
        buttonArea2.Y += buttonAdjustment2.Y;
        buttonArea2.Height -= buttonAdjustment2.Height;
        buttonArea2.Width -= buttonAdjustment2.Width;
        Rectangle buttonArea3 = cellBounds;
        Rectangle buttonAdjustment3 = this.BorderWidths(advancedBorderStyle);
        buttonArea3.X += buttonAdjustment3.X + buttonArea2.Width;
        buttonArea3.Y += buttonAdjustment3.Y;
        buttonArea3.Height -= buttonAdjustment3.Height;
        buttonArea3.Width -= buttonAdjustment3.Width;
        Rectangle buttonArea4 = cellBounds;
        Rectangle buttonAdjustment4 = this.BorderWidths(advancedBorderStyle);
        buttonArea4.X += buttonAdjustment4.X + buttonArea3.Width;
        buttonArea4.Y += buttonAdjustment4.Y;
        buttonArea4.Height -= buttonAdjustment4.Height;
        buttonArea4.Width -= buttonAdjustment4.Width;

        // 绘制禁用的按钮。
        ButtonRenderer.DrawButton(graphics, buttonArea1, PushButtonState.Default);
        ButtonRenderer.DrawButton(graphics, buttonArea2, PushButtonState.Default);
        ButtonRenderer.DrawButton(graphics, buttonArea3, PushButtonState.Default);
        ButtonRenderer.DrawButton(graphics, buttonArea4, PushButtonState.Default);

        // 绘制禁用的按钮文本。
        TextRenderer.DrawText(graphics, "Test", this.DataGridView.Font, buttonArea1, SystemColors.GrayText);
        TextRenderer.DrawText(graphics, "Test", this.DataGridView.Font, buttonArea2, SystemColors.GrayText);
        TextRenderer.DrawText(graphics, "Test", this.DataGridView.Font, buttonArea3, SystemColors.GrayText);
        TextRenderer.DrawText(graphics, "Test", this.DataGridView.Font, buttonArea4, SystemColors.GrayText);
    }

    // 当鼠标指针进入单元格时,强制单元格重新绘制自身。
    protected override void OnMouseEnter(int rowIndex)
    {
       
    }

    // 当鼠标指针离开单元格时,强制单元格重新绘制自身。
    protected override void OnMouseLeave(int rowIndex)
    {
       
    }
}

public class CustomButtonColumn : DataGridViewColumn
{
    public CustomButtonColumn()
    {
        this.CellTemplate = new CustomButtonCell();
    }
}

请注意,上述代码在CustomButtonCell中创建了一个Panel和四个Button,然后在Paint方法中绘制了按钮和按钮文本。希望这对你有所帮助。

英文:

I am trying to have a custom DatagridViewCell that has 3 clickable buttons horizontally. I have gotten as far as I can in code as shown below but I need a way to display the 3 buttons in the cell. I have only been able to paint text so far. I have even tried to declare a Panel object in case that will be easier to manipulate the buttons with.

public partial class CustomButtonCell : DataGridViewButtonCell
{
private Panel buttonPanel;
private Button editButton;
private Button deleteButton;
private Button approveButton;
private Button cancelButton;
public bool Enabled { get; set; }
public CustomButtonCell()
{
this.buttonPanel = new Panel();
this.editButton = new Button();
this.deleteButton = new Button();
this.approveButton = new Button();
this.cancelButton = new Button();
this.editButton.Text = "Edit";
this.deleteButton.Text = "Delete";
this.approveButton.Text = "Approve";
this.cancelButton.Text = "Cancel";
this.buttonPanel.Controls.Add(this.editButton);
this.buttonPanel.Controls.Add(this.deleteButton);
this.buttonPanel.Controls.Add(this.approveButton);
this.buttonPanel.Controls.Add(this.cancelButton);
this.Enabled = true;
}
// Override the Clone method so that the Enabled property is copied.
public override object Clone()
{
CustomButtonCell cell = (CustomButtonCell )base.Clone();
cell.Enabled = this.Enabled;
return cell;
}
protected override void Paint(Graphics graphics,
Rectangle clipBounds, Rectangle cellBounds, int rowIndex,
DataGridViewElementStates elementState, object value,
object formattedValue, string errorText,
DataGridViewCellStyle cellStyle,
DataGridViewAdvancedBorderStyle advancedBorderStyle,
DataGridViewPaintParts paintParts)
{
// Call the base class method to paint the default cell appearance.
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState,
value, formattedValue, errorText, cellStyle,
advancedBorderStyle, paintParts);
// Calculate the area in which to draw the button.
Rectangle buttonArea1 = cellBounds;
Rectangle buttonAdjustment = this.BorderWidths(advancedBorderStyle);
buttonArea1.X += buttonAdjustment.X;
buttonArea1.Y += buttonAdjustment.Y;
buttonArea1.Height -= buttonAdjustment.Height;
buttonArea1.Width -= buttonAdjustment.Width;
Rectangle buttonArea2 = cellBounds;
Rectangle buttonAdjustment2 = this.BorderWidths(advancedBorderStyle);
buttonArea2.X += buttonAdjustment2.X + buttonArea1.Width;
buttonArea2.Y += buttonAdjustment2.Y;
buttonArea2.Height -= buttonAdjustment2.Height;
buttonArea2.Width -= buttonAdjustment2.Width;
Rectangle buttonArea3 = cellBounds;
Rectangle buttonAdjustment3 = this.BorderWidths(advancedBorderStyle);
buttonArea3.X += buttonAdjustment3.X + buttonArea2.Width;
buttonArea3.Y += buttonAdjustment3.Y;
buttonArea3.Height -= buttonAdjustment3.Height;
buttonArea3.Width -= buttonAdjustment3.Width;
Rectangle buttonArea4 = cellBounds;
Rectangle buttonAdjustment4 = this.BorderWidths(advancedBorderStyle);
buttonArea4.X += buttonAdjustment4.X + buttonArea3.Width;
buttonArea4.Y += buttonAdjustment4.Y;
buttonArea4.Height -= buttonAdjustment4.Height;
buttonArea4.Width -= buttonAdjustment4.Width;
// Draw the disabled button.
ButtonRenderer.DrawButton(graphics, buttonArea1, PushButtonState.Default);
ButtonRenderer.DrawButton(graphics, buttonArea2, PushButtonState.Default);
ButtonRenderer.DrawButton(graphics, buttonArea3, PushButtonState.Default);
ButtonRenderer.DrawButton(graphics, buttonArea4, PushButtonState.Default);
// Draw the disabled button text.
TextRenderer.DrawText(graphics, "Test", this.DataGridView.Font, buttonArea1, SystemColors.GrayText);
TextRenderer.DrawText(graphics, "Test", this.DataGridView.Font, buttonArea2, SystemColors.GrayText);
TextRenderer.DrawText(graphics, "Test", this.DataGridView.Font, buttonArea3, SystemColors.GrayText);
TextRenderer.DrawText(graphics, "Test", this.DataGridView.Font, buttonArea4, SystemColors.GrayText);
}
// Force the cell to repaint itself when the mouse pointer enters it.
protected override void OnMouseEnter(int rowIndex)
{
}
// Force the cell to repaint itself when the mouse pointer leaves it.
protected override void OnMouseLeave(int rowIndex)
{
}
}
public class CustomButtonColumn : DataGridViewColumn
{
public CustomButtonColumn()
{
this.CellTemplate = new CustomButtonCell ();
}
}

答案1

得分: 2

我同意有时候展示UserControl有很好的用例,不管是"三个按钮"还是每行都有其自己的实时数据滚动Chart之类的东西!一个长期以来适用于我并经得起考验的方法是,创建一个类似下面示例代码的DataGridViewUserControlColumn类,它可以托管单元格边界内的控件,而不仅仅是绘制一个控件。

操作的理论是允许绑定数据类具有从Control派生的属性。DGV中的相应自动生成列可以被替换。然后,当一个DataGridViewUserControlCell被"绘制"时,不是绘制单元格,而是将控件移动(如果需要的话),使其边界与正在绘制的单元格边界重合。由于用户控件位于DataGridView.Controls集合中,所以UC保持在Z顺序的顶部,并与任何容器的任何子元素一样绘制。

WinForms自定义3按钮单元格

项目的UserControl是第一次绘制时添加到DataGridView.Controls集合中的,当单元格的DataGridView属性设置为null时被移除(例如,当用户删除一行时)。当启用AllowUserToAddRows选项时,"新行"列表项在编辑完成之前不显示控件。

典型的Record类

class Record : INotifyPropertyChanged
{
    // ... 省略部分代码 ...
}

配置DGV

public partial class MainForm : Form
{
    // ... 省略部分代码 ...
}

自定义具有Paint重写的单元格

public class DataGridViewUserControlCell : DataGridViewCell
{
    // ... 省略部分代码 ...
}

自定义列

public class DataGridViewUserControlColumn : DataGridViewColumn
{
    // ... 省略部分代码 ...
}

请注意,以上代码示例中包含了一些省略号,这些省略号代表了未提供的代码部分。如果您需要完整的代码,可以提供更多的上下文信息,我将继续翻译剩余部分。

英文:

I agree that sometimes there's a solid use case to display a UserControl whether it's "three buttons" or each row having its own rolling Chart of real time data or whatever! One approach that long-term has worked for me, tried and true, is having a DataGridViewUserControlColumn class similar to the one coded below that can host a control in the cell bounds instead of just drawing one.

The theory of operation is to allow the bound data class to have properties that derive from Control. The corresponding auto-generated column(s) in the DGV can be swapped out. Then, when a DataGridViewUserControlCell gets "painted" instead of drawing the cell what happens instead is that the control is moved (if necessary) so that its bounds coincide with the cell bounds being drawn. Since the user control is in the DataGridView.Controls collection, the UC stays on top in the z-order and paints the same as any child of any container would.

WinForms自定义3按钮单元格

The item's UserControl is added to the DataGridView.Controls collection the first time it's drawn and removed when the cell's DataGridView property is set to null (e.g. when user deletes a row). When the AllowUserToAddRows options is enabled, the "new row" list item doesn't show a control until the item editing is complete.


Typical Record class

class Record : INotifyPropertyChanged
{
public Record()
{
Modes.TextChanged += (sender, e) =>
OnPropertyChanged(nameof(Description));
Actions.Click += (sender, e) =>
{ _ = execTask(); };
}
public string Description
{
get => $"{Modes.Text} : {_description}";
set
{
if (!Equals(_description, value))
{
_description = value;
OnPropertyChanged();
}
}
}
string _description = string.Empty;
#region B O U N D    C O N T R O L S    o f    A N Y    T Y P E   
public ButtonCell3Up Modes { get; } = new ButtonCell3Up();
public ProgressBar Actions { get; } = new ProgressBar { Value = 1 };  
#endregion B O U N D    C O N T R O L S    o f    A N Y    T Y P E   
private async Task execTask()
{
Actions.Value = 0;
while(Actions.Value < Actions.Maximum)
{
await Task.Delay(250);
Actions.Value++;
}
}
private void onModesTextChanged(object sender, EventArgs e) =>
OnPropertyChanged(nameof(Description));
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

Configure DGV

public partial class MainForm : Form
{
public MainForm() => InitializeComponent();
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
dataGridView.DataSource = Records;
dataGridView.RowTemplate.Height = 50;
dataGridView.MouseDoubleClick += onMouseDoubleClick;  
#region F O R M A T    C O L U M N S
Records.Add(new Record()); // <- Auto-configure columns
dataGridView.Columns[nameof(Record.Description)].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridView.Columns[nameof(Record.Modes)].Width = 200;
DataGridViewUserControlColumn.Swap(dataGridView.Columns[nameof(Record.Modes)]);
dataGridView.Columns[nameof(Record.Actions)].Width = 200;
dataGridView.Columns[nameof(Record.Actions)].DefaultCellStyle.Padding = new Padding(5);
DataGridViewUserControlColumn.Swap(dataGridView.Columns[nameof(Record.Actions)]);
Records.Clear();
#endregion F O R M A T    C O L U M N S
// FOR DEMO PURPOSES: Add some items.
for (int i = 0; i < 5; i++)
{
Records.Add(new Record { Description = "Voltage Range" });
Records.Add(new Record { Description = "Current Range" });
Records.Add(new Record { Description = "Power Range" });
}
for (int i = 1; i <= Records.Count; i++)
Records[i - 1].Modes.Labels = new[] { $"{i}A", $"{i}B", $"{i}C", }; 
}

Custom Cell with Paint override

public class DataGridViewUserControlCell : DataGridViewCell
{
private Control _control = null;
private DataGridViewUserControlColumn _column;
public override Type FormattedValueType => typeof(string);
private DataGridView _dataGridView = null;
protected override void OnDataGridViewChanged()
{
base.OnDataGridViewChanged();
if((DataGridView == null) && (_dataGridView != null))
{
// WILL occur on Swap() and when a row is deleted.
if (TryGetControl(out var control))
{
_column.RemoveUC(control);
}
}
_dataGridView = DataGridView;
}
protected override void Paint(
Graphics graphics,
Rectangle clipBounds,
Rectangle cellBounds,
int rowIndex,
DataGridViewElementStates cellState,
object value,
object formattedValue,
string errorText,
DataGridViewCellStyle cellStyle,
DataGridViewAdvancedBorderStyle advancedBorderStyle,
DataGridViewPaintParts paintParts)
{
using (var brush = new SolidBrush(getBackColor(@default: Color.Azure)))
{
graphics.FillRectangle(brush, cellBounds);
}
if (DataGridView.Rows[rowIndex].IsNewRow)
{   /* G T K */
}
else
{
if (TryGetControl(out var control))
{
SetLocationAndSize(cellBounds, control);
}
}
Color getBackColor(Color @default)
{
if((_column != null) && (_column.DefaultCellStyle != null))
{
Style = _column.DefaultCellStyle;
}
return Style.BackColor.A == 0 ? @default : Style.BackColor;
}
}
public void SetLocationAndSize(Rectangle cellBounds, Control control, bool visible = true)
{
control.Location = new Point(
cellBounds.Location.X +
Style.Padding.Left,
cellBounds.Location.Y + Style.Padding.Top);
control.Size = new Size(
cellBounds.Size.Width - (Style.Padding.Left + Style.Padding.Right),
cellBounds.Height - (Style.Padding.Top + Style.Padding.Bottom));
control.Visible = visible;
}
public bool TryGetControl(out Control control)
{
control = null;
if (_control == null)
{
try
{
if ((RowIndex != -1) && (RowIndex < DataGridView.Rows.Count))
{
var row = DataGridView.Rows[RowIndex];
_column = (DataGridViewUserControlColumn)DataGridView.Columns[ColumnIndex];
var record = row.DataBoundItem;
var type = record.GetType();
var pi = type.GetProperty(_column.Name);
control = (Control)pi.GetValue(record);
if (control.Parent == null)
{
DataGridView.Controls.Add(control);
_column.AddUC(control);
}
}
}
catch (Exception ex) {
Debug.Assert(false, ex.Message);
}
_control = control;
}
else control = _control;
return _control != null;
}
}

Custom Column

public class DataGridViewUserControlColumn : DataGridViewColumn
{
public DataGridViewUserControlColumn() => CellTemplate = new DataGridViewUserControlCell();
public static void Swap(DataGridViewColumn old)
{
var dataGridView = old.DataGridView;
var indexB4 = old.Index;
dataGridView.Columns.RemoveAt(indexB4);
dataGridView.Columns.Insert(indexB4, new DataGridViewUserControlColumn
{
Name = old.Name,
AutoSizeMode = old.AutoSizeMode,
Width = old.Width,
DefaultCellStyle = old.DefaultCellStyle,
});
}
protected override void OnDataGridViewChanged()
{
base.OnDataGridViewChanged();
if ((DataGridView == null) && (_dataGridView != null))
{
_dataGridView.Invalidated -= (sender, e) => refresh();
_dataGridView.Scroll -= (sender, e) => refresh();
_dataGridView.SizeChanged -= (sender, e) => refresh();
foreach (var control in _controls.ToArray())
{
RemoveUC(control);
}
}
else
{
DataGridView.Invalidated += (sender, e) =>refresh();
DataGridView.Scroll += (sender, e) =>refresh();
DataGridView.SizeChanged += (sender, e) =>refresh();
}
_dataGridView = DataGridView;
}
// Keep track of controls added by this instance
// so that they can be removed by this instance.
private readonly List<Control> _controls = new List<Control>();
internal void AddUC(Control control)
{
_controls.Add(control);
DataGridView.Controls.Add(control);
}
internal void RemoveUC(Control control)
{
_controls.Remove(control);
if (_dataGridView != null)
{
_dataGridView.Controls.Remove(control);
}
}
int _wdtCount = 0;
private void refresh()
{
var capture = ++_wdtCount;
// Allow changes to settle.
Task
.Delay(TimeSpan.FromMilliseconds(10))
.GetAwaiter()
.OnCompleted(() => 
{
if (DataGridView != null)
{
foreach (var row in DataGridView.Rows.Cast<DataGridViewRow>().ToArray())
{
if (row.Cells[Index] is DataGridViewUserControlCell cell)
{
if (row.IsNewRow)
{   /* G T K */
}
else
{
var cellBounds = DataGridView.GetCellDisplayRectangle(cell.ColumnIndex, cell.RowIndex, true);
if (cell.TryGetControl(out var control))
{
cell.SetLocationAndSize(cellBounds, control, visible: !row.IsNewRow);
}
}
}
}
}
});
}
private DataGridView _dataGridView = null;
}

huangapple
  • 本文由 发表于 2023年2月7日 01:34:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/75364694.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定