I have a datatable
that looks like the following:
Room Cook Waiter BG_Image
----------------------------------
201 Joe Jim Green.png
Cloning your design is not hard; and using some sort of DataBinding
to your DataTable
to display the data can be achieved with a little workaround.
Things will get trickier, read: need more effort, when you want more than simply displaying the data. I will not go into that here.
Here is a tiny example:
The code below only lets your display the data. There is no provision for any of the advanced functionality you can get from DataBinding
like navigation, editing, validation..
The issue at hand really consists of three separate problems:
DataBinding
to multiple records.For the latter there are many options going from a owner-drawn Panel
subclass to a custom-made UserControl
. Let's go for the latter as it is simple to code and easier to expand.
For the second we can choose between several container controls. I'll go for a FlowLayoutPanel
.
But how can we use DataBinding
? Even if we bind members of the UC we still don't get multi-record data-binding.
The workaround uses the one Winforms
control I can think of that provides multi-record data-binding: A DatGridView
. But as it will only display a grid of cells with rows and columns, each cell holding one value, we won't let it display anything at all:
DataGridView dataGridView1 = new DataGridView();
DataTable DT = null;
public Form1()
{
InitializeComponent();
dataGridView1.DataSourceChanged += dataGridView1_DataSourceChanged;
}
As you can see the DGV
is created strictly with the default values but not added to the Form
, so it doesn't show.
We only use its DataSource
(after loading the DataTable
, obvioulsy):
private void button1_Click(object sender, EventArgs e)
{
dataGridView1.DataSource = DT;
}
When the DataSource
changes we clear the FlowLayoutPanel
we use to host the display controls and add one for each DataRow
:
private void dataGridView1_DataSourceChanged(object sender, EventArgs e)
{
DataTable dt = (DataTable)dataGridView1.DataSource;
flowLayoutPanel1.Controls.Clear();
foreach (DataRow row in dt.Rows)
{
UCBind ucb = new UCBind(row, imageList1);
flowLayoutPanel1.Controls.Add(ucb);
}
}
The FlowLayoutPanel
makes life very easy; just make sure its size can hold the right number of display controls! But you could also add them to a TableLayoutPanel
or even a simple Panel
. For these you need to but also can determine all the layout details yourself.
Now for the display control.
I have created a UserControl UCBind
and pass in a DataRow
and an ImageList
in the constructor. The code looks like this:
public partial class UCBind : UserControl
{
ImageList imgList { get; set; }
public UCBind()
{
InitializeComponent();
DoubleBuffered = true; // prevent flicker
}
public UCBind(DataRow row, ImageList imglist)
{
InitializeComponent();
DoubleBuffered = true; // prevent flicker
if (row != null)
{
imgList = imglist;
DisplayData(row);
}
}
public void DisplayData(DataRow row)
{
lbl_field1.Text = row.Field<int>(0) + "";
lbl_field2.Text = row.Field<string>(1);
lbl_field3.Text = row.Field<string>(2);
if (imgList != null) BackgroundImage = imgList.Images[row.Field<string>(3)];
}
}
Obviously there is a little more going on in the designer code to style the UC and its fields:
Labels
Dock
each to the Top
.AutoSize = false
.Height
ForeColor
to White
.Fonts
as needed.TextAlignment
to Left, Right & Right
.BackColors
to Transparent
.BackgroundImageLayout
to Stretch
.You will also want to set the Size
to what you want.
I have not included the designer genrated code as it tends to be rather long. Simply create the new UserControl
class yourself, add the Labels
, styling and the few lines of constructor code above..!
And to make it rich you could try to add resizing code to make the FLP
always fill with the right number of controls.
Obviously the rounded corners and 3d borders will look best if you don't distort the image. I use an ImageList
with suitable values for ColorDepth
and ImageSize
and load the three Images
. Then I pass a reference to it to each UC
I create.. The ImageList is added to the form and you can load the images into it right in the VS designer..:
Here's some VB code to add an array of textboxes to a FlowLayoutPanel:
Sub AddMany(cols As Integer, rows As Integer)
Dim txt As TextBox
FlowLayoutPanel1.FlowDirection = FlowDirection.LeftToRight
FlowLayoutPanel1.AutoScroll = True
For y As Integer = 0 To rows - 1
For x As Integer = 0 To cols - 1
If Not (x = 0 And y = 0) Then
txt = New TextBox
txt.Text = "txt_" & y & "_" & x
txt.Name = "txt_" & y & "_" & x
FlowLayoutPanel1.Controls.Add(txt)
AddHandler txt.KeyDown, AddressOf MoveKeyDown
AddHandler txt.DoubleClick, AddressOf ShowMeta
End If
Next
FlowLayoutPanel1.SetFlowBreak(txt, True)
Next
End Sub
Instead of simple TextBoxes you could add Buttons with images or PictureBoxes. You'd have to paint the text onto the images - or use a custom control.
I'd say go for a 3rd party control library like devexpress (https://documentation.devexpress.com/#WindowsForms/CustomDocument3466) or telerik.
It is really easy to build exactly the same output as your image using WPF. WPF controls are easy to host in a WinForms environment using an ElementHost (see https://dzone.com/articles/hosting-wpf-controls-winforms for an example or just google it)
Hints for WPF: use a WrapPanel as container. See http://www.dotnetperls.com/wrappanel for an example.