引自:http://www.codeproject.com/books/PresentDataDataGridView.asp | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Presenting Data with the DataGridView ControlIntroductionThe preceding chapters showed many detailed examples of data binding to simple bound controls and list bound controls. However, one of the most common ways of presenting data is in tabular form. Users are able to quickly scan and understand large amounts of data visually when it is presented in a table. In addition, users can interact with that data in a number of ways, including scrolling through the data, sorting the data based on columns, editing the data directly in the grid, and selecting columns, rows, or cells. In .NET 1.0, the DataGridView OverviewThe You can make the grid act like a spreadsheet, so that the focus for interaction and presentation is at the cell level instead of at the row or column level. You can control the formatting and layout of the grid with fine-grained precision simply by setting a few properties on the control. Finally, you can plug in a number of predefined column and cell control types, or provide your own custom controls, and you can even mix different control types within different cells in the same row or column. Figure 6.1 shows an example of a Basic Data Binding with the DataGridViewThe easiest way to get started using the private void OnFormLoad(object sender, EventArgs e)
{
// Create adapter to get data source
CustomersTableAdapter adapter = new CustomersTableAdapter();
// Bind table data source to binding source
m_CustomersBindingSource.DataSource = adapter.GetData();
// Bind the binding source to grid control
m_Grid.DataSource = m_CustomersBindingSource;
}
Alternatively, if the binding source is bound to a collection of data collections, such as a data set, then you can refine what part of the data source you want to bind to using the private void OnFormLoad(object sender, EventArgs e)
{
// Create adapter to get data source
CustomersTableAdapter adapter = new CustomersTableAdapter();
// Get data set instance
CustomersDataSet customers = new CustomersDataSet();
// Fill data set
adapter.Fill(customers);
// Bind binding source to the data set
m_CustomersBinding source.DataSource = customers;
// Bind grid to the Customers table within the data source
m_Grid.DataSource = m_CustomersBinding source;
m_Grid.DataMember = "Customers";
}
For basic data-binding scenarios, the The The Any time the Like most properties on the Controlling Modifications to Data in the GridThe If you want to disallow any of these interactions, set the Programmatic DataGridView ConstructionThe most common way of using the grid is with data-bound columns. When you bind to data, the grid creates columns based on the schema or properties of the data items, and generates rows in the grid for each data item found in the bound collection. If the data binding was set up statically using the designer (as has been done in most of the examples in this book), the types and properties of the columns in the grid were set at design time. If the data binding is being done completely dynamically, the The first thing to realize is that like all .NET controls, a grid on a form is just an instance of a class. That class contains properties and methods that you can use to code against its contained object model. For Programmatically Adding Columns to a GridThere are a number of ways to approach programmatically adding columns and rows to the grid. The first step is to define the columns from which the grid is composed. To define a column, you have to specify a cell template on which the column is based. The cell template will be used by default for the cells in that column whenever a row is added to the grid. Cell templates are instances of a For now, let’s stick with the most common and simple cell type, a
When you set the However, when adding text box columns, if you specify more columns than the current number of columns, additional text box columns will be added to the right of the existing columns to bring the column count up to the number you specify. This is a compact way to add some columns for dynamic situations. Programmatically Adding Rows to a GridOnce you have added columns to the grid, you can programmatically add rows to the grid as well. In most cases, this involves using the // Add a row
m_Grid.Rows.Add(); Several overloads of the private void OnAddHeterows(object sender, EventArgs e)
{
// Create 5 text box columns
m_Grid.ColumnCount = 5;
DataGridViewRow heterow = new DataGridViewRow();
DataGridViewComboBoxCell comboCell =
new DataGridViewComboBoxCell();
comboCell.Items.Add("Black");
comboCell.Items.Add("White");
comboCell.Value = "White";
heterow.Cells.Add(comboCell);
for (int i = 0; i < 4; i++)
{
heterow.Cells.Add(new DataGridViewTextBoxCell());
}
// this row has a combo in first cell
m_Grid.Rows.Add(heterow);
} To add a row to a grid this way, the grid must already have been initialized with the default set of columns that it will hold. Additionally, the number of cells in the row that is added must match that column count. In this code sample, five text box columns are implicitly added by specifying a column count of five, then the first row is added as a heterogeneous row by constructing the cells and adding them to the row before adding the row to the grid. You can also save yourself some code by using the grid’s existing column definitions to create the default set of cells in a row using the DataGridViewRow heterow = new DataGridViewRow();
heterow.CreateCells(m_Grid);
heterow.Cells.RemoveAt(0);
heterow.Cells.Insert(0, new DataGridViewComboBoxCell());
m_Grid.Rows.Add(heterow); To access the contents of the cells programmatically, you index into the Once you have a reference to the cell, you can do anything with that cell that the actual cell type supports. If you want to access specific properties or methods exposed by the cell type, you have to cast the reference to the actual cell type. To change a cell’s contents, you set the For example, if you want to set the contents of a text box cell to the current date using the short date format, you could use the following code: m_Grid.Rows[0].Cells[2].Value = DateTime.Now;
m_Grid.Rows[0].Cells[2].Style.Format = "d"; This sets the value of the third cell in the first row to an instance of a Custom Column Content with Unbound ColumnsNow that you understand how to programmatically create columns and rows, and populate them with values, you may be wondering if you have to go to all that trouble any time you want to present content in a cell that isn’t bound to data. The good news is that there is a faster way for most scenarios where you want to present unbound data. You will need to programmatically create all the columns in the grid (although you can get the designer to write this code for you too, as shown later), but you can use events to make populating the values a little easier, especially when you mix bound data with unbound columns. An unbound column is a column that isn’t bound to data. You add unbound columns to a grid programmatically, and you populate the column’s cells either programmatically as shown in the previous section or by using events as shown in this section. You can still add columns to the grid that are automatically bound to columns or properties in the data items of the data source. You do this by setting the There are two primary ways to populate the contents of unbound columns: handling the To demonstrate this capability, let’s look at a simple example.
It should be noted that the code example for the If you want to set the actual stored cell values of unbound columns in the grid, a better way to do this is to handle the private void OnRowsAdded(object sender,
DataGridViewRowsAddedEventArgs e)
{
for (int i = 0; i < e.RowCount; i++)
{
DataGridViewRow row = m_CustomersGrid.Rows[e.RowIndex + i];
row.Cells["Contact"].Value = string.Format("{0} : {1}",
row.Cells["ContactName"].Value,
row.Cells["Phone"].Value);
}
} This code obtains the current row being set in the loop by indexing into the
Displaying Computed Data in Virtual ModeAnother scenario where you may want to take explicit control over providing a display value for each cell is when you are working with data that contains computed values, especially in combination with bound data and large sets of data. For example, you may need to present a collection of data that has tens of thousands or even millions of rows in a grid. You really ought to question the use case first before supporting such a thing, but if you really need to do this, you probably don’t want to clobber your system’s memory by creating multiple copies of that data. You may not even want to bring it all into memory at one time, especially if this data is being dynamically created or computed. However, you will want users to able be to smoothly scroll through all that data to locate the items of interest. Virtual mode for the When you choose to use virtual mode, you can either provide all of the row values through virtual mode event handling, or you can mix the columns of the grid with data-bound and unbound columns. You have to define the columns that will be populated through virtual mode as described earlier in the “Programmatic DataGridView Construction” section. If you also data bind some columns, then the rows will be populated through data binding and you just handle the events necessary for virtual mode for the columns that aren’t data bound. If you aren’t data binding, you have to add as many rows as you expect to present in the grid, so the grid knows to scale the scrollbar appropriately. You then need a way to get the values corresponding to virtual mode columns when they are needed for presentation. You can do this by computing the values dynamically when they are needed, which is one of the main times that you would use virtual mode. You might also use cached client-side data in the form of an object collection or data set, or you might actually make round-trips to the server to get the data as needed. With the latter approach, you’ll need a smart preload and caching strategy, because you could quickly bog down the application if data queries have to run between the client and the server while the user is scrolling. If the data being displayed is computed data, then it really makes sense to wait to compute values until they are actually going to be displayed. Setting Up Virtual ModeThe following steps describe how to set up virtual mode data binding.
The code in Listing 6.2 shows a simple Windows Forms application that demonstrates the use of virtual mode. A Listing 6.2: Virtual Mode Sample partial class VirtualModeForm : Form
{
private List<DataObject> m_Data = new List<DataObject>();
private List<bool> m_Visited = new List<bool>();
public VirtualModeForm()
{
InitializeComponent();
m_Grid.CellValueNeeded += OnCellValueNeeded;
m_GetVisitedCountButton.Click += OnGetVisitedCount;
InitData();
InitGrid();
}
private void InitData()
{
for (int i = 0; i < 1000001; i++)
{
m_Visited.Add(false);
DataObject obj = new DataObject();
obj.Id = i;
obj.Val = 2 * i;
m_Data.Add(obj);
}
}
private void InitGrid()
{
m_Grid.VirtualMode = true;
m_Grid.ReadOnly = true;
m_Grid.AllowUserToAddRows = false;
m_Grid.AllowUserToDeleteRows = false;
m_Grid.ColumnCount = 3;
m_Grid.Rows.Add();
m_Grid.Rows.AddCopies(0, 1000000);
// Uncomment the next line and comment out the
// the rest of the method to switch to data bound mode
// m_Grid.DataSource = m_Data;
}
private void OnCellValueNeeded(object sender,
DataGridViewCellValueEventArgs e)
{
m_Visited[e.RowIndex] = true;
if (e.ColumnIndex == 0)
{
e.Value = m_Data[e.RowIndex].Id;
}
else if (e.ColumnIndex == 1)
{
e.Value = m_Data[e.RowIndex].Val;
}
else if (e.ColumnIndex == 2)
{
Random rand = new Random();
e.Value = rand.Next();
}
}
private void OnGetVisitedCount(object sender, EventArgs e)
{
int count = 0;
foreach (bool b in m_Visited)
{
if (b) count++;
}
MessageBox.Show(count.ToString());
}
}
public class DataObject
{
private int m_Id;
private int m_Val;
public int Val
{
get { return m_Val; }
set { m_Val = value; }
}
public int Id
{
get { return m_Id; }
set { m_Id = value; }
}
} The form constructor starts by calling The first event handler is the important one for virtual mode—the After that, the constructor calls the
If you are thinking of presenting millions of rows of data in a grid, and you expect to let users edit the rows in the grid and let the data set updating capabilities propagate those changes back to your data store, then the Object collections like the List generic collection are significantly more lightweight than a dataset and result in less memory consumption and quicker construction times. If you are going to cache a large collection in memory, you should try to design the form to use a lightweight object collection to cache the values for display only. Initializing the GridAfter the data is initialized, the constructor calls the
After that, Windows Forms event handling takes over for the rest of the application lifecycle. Because the grid was set to virtual mode, the next thing that happens is the Understanding Virtual Mode BehaviorIf you run the The When dealing with very large data sets, if the user drags the scrollbar thumb control, a large numbers of rows are actually skipped through the paging mechanisms and latency of the scroll bar. As a result, you only have to supply a tiny percentage of the actual cell values unless the user does an extensive amount of scrolling in the grid. This is why virtual mode is particularly nice for computed values: you can avoid computing cell values that won’t be displayed. If you run this example and scroll around for a bit, then click the Get Visited Count button, you will see how many rows were actually loaded. For example, I ran this application and scrolled through the data from top to bottom fairly slowly several times. While doing so, I saw smooth scrolling performance that looked like I was actually scrolling through the millions of rows represented by the grid. However, in reality, only about 1,000 rows were actually rendered while I was scrolling. What if you want to support editing of the values directly in the grid? Maybe you are just using virtual mode to present a computed column with a relatively small set of data, and you want to use that column’s edited value to perform some other computation or store the edited value. Another event, Virtual Mode SummaryThat’s all there is to virtual mode: set the Using the Built-In Column TypesUsing a text box column is straightforward enough: you data bind to something that can be rendered as text, or set the The first thing to realize is that even though most of the functionality is surfaced at the cell level in a As a result, for each of the built-in cell types that the grid is capable of displaying, there is a corresponding column type designed to contain cells of that type. Each cell type is derived from the Because each built-in column type is different in subtle ways, it’s best to cover them one at a time. However, since all of the cell types contained by the column types derive from the same base class, there are a number of properties from the base class that you’ll use for controlling and accessing cell content. The properties of the
The
There are a number of built-in column types that are available for using with the DataGridViewTextBoxColumnThis is the default type of column (as described earlier in this chapter), and it displays text within the contained cells, which are of type This column type supports editing if the DataGridViewButtonColumnThis column type displays cells of type DataGridViewLinkColumnLike the button column, this is another form of rendering a text cell that gives the user a visual cue that clicking on it will invoke some action. This column type contains cells of type DataGridViewCheckBoxColumnBy now you are probably picking up the pattern, and as you would guess, this column type contains cells of type The values that this cell type supports depend on whether you set the cell or column type into DataGridViewImageColumnThis column, not surprisingly, contains cells of type In addition to setting a default image at the column level, you can set the
DataGridViewComboBoxColumnThis column type contains cells of type
The combo box cells support edit mode, and users can type in a value for auto-completion purposes or select values from a drop-down list. When in edit mode, this cell type hosts a control that derives from the The Data binding this column type or the cells in it works just like data binding a standalone toCountryColumn.DataSource = m_CountriesBindingSource;
toCountryColumn.DisplayMember = "CountryName";
toCountryColumn.ValueMember = "CountryID"; The sample code that accompanies this chapter contains a simple application called Built-In Header CellsHeader cells are the cells rendered at the top and left sides of the grid. They provide the context or a guide to what the cells in the grid contain. Column header cells are of type The row header cells are of type Both column and row headers can be further customized by implementing custom painting by handling the private void OnCellPainting(object sender,
DataGridViewCellPaintingEventArgs e)
{
if (e.ColumnIndex < 0)
{
e.Graphics.FillRectangle(Brushes.Aqua, e.CellBounds);
e.Handled = true;
}
} This code checks to see if the column being painted has an index less than zero, which indicates that the row header is being painted. The column index of the row headers is –1, and the row index of the column headers is also –1. You cannot index into the Additionally, you can set the Handling Grid Data EditsHow you handle grid edits is going to depend on the following:
As mentioned earlier, when working with a text box column, users can start editing a cell by putting the focus into the cell with the mouse, arrow keys, or by pressing the F2 key when the mouse pointer is in the cell. If users then start typing characters, the current contents of the cell will be overwritten. When they change the focus to another cell, this completes the editing process. The first thing that happens that you might want to handle is that the If the cell is data bound, and if the data source supports editing the data objects in the collection, the data will automatically be pushed back into the underlying data source. If the cell is a button or link cell, however, you won’t be able to edit the contents in the first place because they don’t support editing. If the cell is a combo box cell, editing is done by selecting a value in the drop-down list or overtyping the current selection if the cell has its When a cell switches into edit mode, an event named public Form1()
{
InitializeComponent();
m_Grid.EditingControlShowing += OnEditControlShowing();
}
private void OnEditControlShowing(object sender,
DataGridViewEditingControlShowingEventArgs e)
{
if (m_Grid.CurrentCell.ColumnIndex == 2)
{
m_HookedCombo = e.Control as ComboBox;
if (m_HookedCombo == null)
return;
m_HookedCombo.SelectedIndexChanged +=
OnCountryComboChanged;
}
}
void OnCountryComboChanged(object sender, EventArgs e)
{
string countryName =
(string)m_Grid.CurrentCell.EditedFormattedValue;
if (string.IsNullOrEmpty(countryName))
return;
DataRow[] countries =
m_MoneyData.Countries.Select(string.Format("CountryName" +
" = '{0}'", countryName));
if (countries != null && countries.Length > 0)
{
MoneyDBDataSet.CountriesRow row =
countries[0] as MoneyDBDataSet.CountriesRow;
int flagColIndex = m_Grid.Columns["TargetCountryFlag"].Index;
DataGridViewCell cell = m_Grid.CurrentRow.Cells[flagColIndex];
cell.Value = row.Flag;
}
} This code does the following:
Keep in mind that the Automatic Column SizingOne of the
One of the most useful values is As a simple example of using this feature, the following code modifies the code from Listing 6.1 to set the column width of the Full Name computed column: newColIndex =
m_AuthorsGrid.Columns.Add("FullName", "Full Name");
m_AuthorsGrid.Columns[newColIndex].AutoSizeMode =
DataGridViewAutoSizeColumnMode.AllCells; The Each column also has a public Form1()
{
InitializeComponent();
m_CustomersGrid.Columns["CustomerID"].Width = 75;
m_CustomersGrid.Columns["CompanyName"].AutoSizeMode =
DataGridViewAutoSizeColumnMode.Fill;
m_CustomersGrid.Columns["CompanyName"].FillWeight = 10;
m_CustomersGrid.Columns["ContactName"].AutoSizeMode =
DataGridViewAutoSizeColumnMode.Fill;
m_CustomersGrid.Columns["ContactName"].FillWeight = 20;
} As a result, the remaining two columns occupy 33 percent and 67 percent of the remaining grid width, respectively, after the Column and Row FreezingScrolling is inevitable when dealing with lots of rows or columns of data. Often when you scroll through data, it is easy to lose the context of what rows or columns you are looking at, especially if that context is based on the values in some other rows or columns. Let’s say you are browsing through a grid filled with product information. If there are a lot of columns of data associated with each product, as you scroll to the right to view columns that aren’t currently displayed, you will lose the context of the product name as it gets scrolled off the left of the screen. What you would really want in this situation is to be able to freeze the product name column so that it is always shown and only have the remaining columns scroll. Likewise, there may be situations where you need to present one or more rows at the top of the grid that need to remain in place as you scroll down to additional rows in the grid. Accomplishing this with the The following code shows a simple example of freezing both a column and a row and setting the divider width: m_ProductsGrid.Columns["ProductName"].Frozen = true;
m_ProductsGrid.Columns["ProductName"].DividerWidth = 3;
m_ProductsGrid.Rows[1].Frozen = true;
m_ProductsGrid.Rows[1].DividerHeight = 3; Using the Designer to Define GridsNow that you understand how to code most of the common uses of the grid, let’s cover how to avoid having to write a lot of that code yourself. The For starters, if you have defined a data source in your project, you can simply drag a data collection source like a data table onto the form designer and a The Choose Data Source drop-down displays a data sources selection window similar to the one described in Chapter 5 for the Properties window. The presented data sources will be tailored to only those that implement the The Edit Columns and Add Column links display dialogs that let you define the columns that the grid will contain, shown in figures 6.4 and 6.5 respectively. The Edit Columns dialog lets you add and remove columns, set the order of the columns within the grid, and set all the design-time properties for a defined column in a focused dialog. The properties shown in the dialog will be tailored based on whether the column is a bound or unbound column, and will expose additional properties based on the column type and cell type. If you define custom column types and include them in your project, they will show up as options for new columns or for configuring columns through this dialog. The Add Column dialog (see Figure 6.5) lets you add a new data-bound or unbound column to the grid. If you are adding a data-bound column, you can select from the columns available in the currently selected data source. You will first have to set the data source to an appropriate collection of data either through the Smart Tag or through the Configuring the columns through these dialogs writes all the code for you that has been covered earlier in this chapter for defining columns and controlling their runtime behavior. The Enable Adding check box on the The In addition to the common properties and behaviors that you can configure through the Smart Tag, there are a bunch of other properties and events that you can configure at design time through the Properties window. Setting any of these properties generates appropriate code in the designer-generated partial class for the form in the Column ReorderingColumn reordering is a slick built-in behavior of the grid that lets users change the display order of columns in the grid at runtime. Because different users of an application often pay more attention to some columns in a grid than others, users commonly request to set the order of the columns displayed in the grid themselves. While you could support this functionality by programmatically removing columns from the grid and then inserting them back in the new position, that requires a fair amount of tedious code to have to write for a common use case. So the Windows Client team was nice enough to build functionality for this right into the grid control. The way this works is that if the When a column has been moved through column reordering, its Listing 6.3: Persisting Display Order of Columns public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
m_Grid.AllowUserToOrderColumns = true;
SetDisplayOrder();
}
private void OnFormClosing(object sender, FormClosingEventArgs e)
{
CacheDisplayOrder();
}
private void CacheDisplayOrder()
{
IsolatedStorageFile isoFile =
IsolatedStorageFile.GetUserStoreForAssembly();
using (IsolatedStorageFileStream isoStream =
new IsolatedStorageFileStream("DisplayCache",
FileMode.Create, isoFile))
{
int[] displayIndices =new int[m_Grid.ColumnCount];
for (int i = 0; i < m_Grid.ColumnCount; i++)
{
displayIndices[i] = m_Grid.Columns[i].DisplayIndex;
}
XmlSerializer ser = new XmlSerializer(typeof(int[]));
ser.Serialize(isoStream,displayIndices);
}
}
private void SetDisplayOrder()
{
IsolatedStorageFile isoFile =
IsolatedStorageFile.GetUserStoreForAssembly();
string[] fileNames = isoFile.GetFileNames("*");
bool found = false;
foreach (string fileName in fileNames)
{
if (fileName == "DisplayCache")
found = true;
}
if (!found)
return;
using (IsolatedStorageFileStream isoStream = new
IsolatedStorageFileStream("DisplayCache",
FileMode.Open, isoFile))
{
try
{
XmlSerializer ser = new XmlSerializer(typeof(int[]));
int[] displayIndicies = (int[])ser.Deserialize(isoStream);
for (int i = 0; i < displayIndicies.Length; i++)
{
m_Grid.Columns[i].DisplayIndex = displayIndicies[i];
}
}
catch { }
}
}
} This code isn’t specific to the data source in any way. The key facets here are that the code in the form
Defining Custom Column and Cell TypesWith the If you want to customize just the painting process of a cell, but you don’t need to add any properties or control things at the column level, you have an event-based option rather than creating new column and cell types. You can handle the The model you should follow for plugging in custom column types matches what you have already seen for the built-in types: You need to create a column type and a corresponding cell type that the column will contain. You do this by simply inheriting from the base The best way to explain this in detail is with an example. Say I wanted to implement a custom column type that lets me display the status of the items represented by the grid’s rows. I want to be able to set a status using a custom-enumerated value, and cells in the column will display a graphic indicating that status based on the enumerated value set on the cell. To do this, I define a Defining a Custom Cell TypeTo achieve this, the first step is to define the custom cell type. If you are going to do your own drawing, you can override the protected virtual I also want the cell value to be able to handle integers as long as they are within the corresponding numeric values of the enumeration, so that I can support the common situation where enumerated types are stored in a database as their corresponding integer values. The code in Listing 6.4 shows the Listing 6.4: Custom Cell Class namespace CustomColumnAndCell
{
public enum StatusImage { Green, Yellow, Red }
public class StatusCell : DataGridViewImageCell
{
public StatusCell()
{
this.ImageLayout = DataGridViewImageCellLayout.Zoom;
}
protected override object GetFormattedValue(object value,
int rowIndex, ref DataGridViewCellStyle cellStyle,
TypeConverter valueTypeConverter,
TypeConverter formattedValueTypeConverter,
DataGridViewDataErrorContexts context)
{
string resource = "CustomColumnAndCell.Red.bmp";
StatusImage status = StatusImage.Red;
// Try to get the default value from the containing column
StatusColumn owningCol = OwningColumn as StatusColumn;
if (owningCol != null)
{
status = owningCol.DefaultStatus;
}
if (value is StatusImage || value is int)
{
status = (StatusImage)value;
}
switch (status)
{
case StatusImage.Green:
resource = "CustomColumnAndCell.Green.bmp";
break;
case StatusImage.Yellow:
resource = "CustomColumnAndCell.Yellow.bmp";
break;
case StatusImage.Red:
resource = "CustomColumnAndCell.Red.bmp";
break;
default:
break;
}
Assembly loadedAssembly = Assembly.GetExecutingAssembly();
Stream stream = loadedAssembly.GetManifestResourceStream(resource);
Image img = Image.FromStream(stream);
cellStyle.Alignment = DataGridViewContentAlignment.TopCenter;
return img;
}
}
} The first declaration in this code is the enumeration The key thing a custom cell type needs to do is either override the Once the status value to be rendered is determined, the Defining a Custom Column TypeSo now you have a custom cell class that could be used in the grid, but you also want to have a custom column class that contains Listing 6.5: Custom Column Class namespace CustomColumnAndCell
{
public class StatusColumn : DataGridViewColumn
{
public StatusColumn() : base(new StatusCell()) { }
private StatusImage m_DefaultStatus = StatusImage.Red;
public StatusImage DefaultStatus
{
get { return m_DefaultStatus; }
set { m_DefaultStatus = value; }
}
public override object Clone()
{
StatusColumn col = base.Clone() as StatusColumn;
col.DefaultStatus = m_DefaultStatus; return col;
}
public override DataGridViewCell CellTemplate
{
get
{
return base.CellTemplate;
}
set
{
if ((value == null) || !(value is StatusCell))
{
throw new ArgumentException( "Invalid" +
" cell type, StatusColumns " +
"can only contain StatusCells");
}
}
}
}
} You can see from the implementation of The next thing the class does is define a public property named Another important thing for you to do in your custom column type is to override the The last thing the custom column class does is to override the Now that you have defined the custom cell and column types, how can you use them? Well, you can define them as part of any Windows application project type in Visual Studio, but generally when you create something like this, you are doing it so you can reuse it in a variety of applications. Whenever you want reuse code, you need to put that code into a class library. So if you define a class library project, add the classes just discussed to the class library, along with the images you want to use for displaying status as embedded resources in the project. This creates an assembly that you can then reference from any Windows application that you want to use the column and cell type within. All you need to do is set a reference to that assembly from the Windows Forms project in which you want to use them, and the custom column types will display in the Add Column dialog, as shown in Figure 6.8 ( Within your Windows Forms application, you can programmatically add With a column of this type added to the grid, you can either populate the grid programmatically with either of the types that the cell is able to handle for values (either and a m_Grid.Rows.Add("Beer Bottler", StatusImage.Green);
m_Grid.Rows.Add("Beer Bottle Filler", 1); //StatusImage.Yellow = 1
m_Grid.Rows.Add("Bottle capper", StatusImage.Red); The CustomColumnAndCell sample application in the download code also demonstrates creating a data set and data binding against the status column. Utilizing Cell-Oriented Grid FeaturesYou have probably noticed that the Once again, the Additionally, there are different selection modes that you can use to change the way the grid highlights cells, columns, or rows when the user clicks in different places in the grid. The
As an example of a more cell-oriented application, the download code includes an application called SimpleSpread. This application mimics a simple spreadsheet and lets you do summations of the numeric values in a cell. It uses a combination of selection mode and some event handling to give you a similar selection experience to most spreadsheets—specifically, it acts like a combination of As you can see, the application lets you enter numbers into the cells; then you can select a sequence of cells and press the Sum button in the tool strip control at the top to get it to calculate the sum and place that in the next cell to the right or below the sequence of selections. As Figure 6.10 shows, this application even supports selecting rectangular groups of cells, and it will compute the summation in both the row and column directions. The logic is nowhere near complete to handle all combinations of selections and cell contents, but it gives you a good idea of how to set something like this up. To code this up (as shown in Listing 6.6), I had to do a few things that are different from your average Listing 6.6: Spreadsheet-Oriented Grid Column Selection Support public partial class SimpleSpreadForm : Form
{
public SimpleSpreadForm()
{
InitializeComponent();
m_Grid.SelectionMode =
DataGridViewSelectionMode.RowHeaderSelect;
}
private void OnFormLoad(object sender, EventArgs e)
{
int start = (int)'A';
for (int i = 0; i < 26; i++)
{
string colName = ((char)(i + start)).ToString();
int index = m_Grid.Columns.Add(colName, colName);
m_Grid.Columns[i].SortMode =
DataGridViewColumnSortMode.NotSortable;
m_Grid.Columns[i].Width = 75;
}
for (int i = 0; i < 50; i++)
{
m_Grid.Rows.Add();
}
}
private void OnColumnHeaderMouseClick(object sender,
DataGridViewCellMouseEventArgs e)
{
m_Grid.ClearSelection();
foreach (DataGridViewRow row in m_Grid.Rows)
{
row.Cells[e.ColumnIndex].Selected = true;
}
}
...
} In this case, I just programmatically added some rows and columns to the grid, set the column headers to be the letters of the alphabet, and turned off sorting on the column by setting the To get the row numbers to display in the row headers, I handled the private void OnRowAdded(object sender,
DataGridViewRowsAddedEventArgs e)
{
m_Grid.Rows[e.RowIndex].HeaderCell.Value =
e.RowIndex.ToString();
} Another selection mode you might want to support is to have hot cells, meaning that the selection of cells changes as you move the mouse around the grid without having to click. To do this, you could just handle the Formatting with StylesThe last topic I want to cover about the As can be seen in Figure 6.11, the lowest layer in the model is the You can set these properties programmatically by accessing the appropriate property member on the instance of the grid, column, row, or cell. All of these properties are of type Using the property fields in this dialog, you can set fine-grained options for how the cell will display its content, and you can even see what it is going to look like in the Preview pane at the bottom of the dialog. You can also set border styles for cells using the grid’s However, you will still hit some limitations in using cell styles. For example, a natural next step for the grid shown in Figure 6.13 would be to set the border colors on the cells that have been colored in to show a black border. However, there is really no way to accomplish this just through cell styles, since the border styles available are only 3D effects and are applied at the grid level for entire cells, not for individual sides of a cell. But, as always, you can almost always accomplish what you need through custom painting or custom cell type definition. Where Are We?This chapter has covered all the main features of the Some key takeaways from this chapter are:
Next we’ll go deeper into the data-binding mechanisms used in Windows Forms, specifically the interfaces that you need to understand and implement in order to create any custom data objects or collections that you want to use for data binding. You will gain a better understanding of what the data-bound controls are looking for when you set up data binding, and how they use what they find. . If you select the grid and display its Smart Tag, as shown in Figure 6.3, you can modify the most common options of the grid’s appearance and behavior from there.
The Choose Data Source drop-down displays a data sources selection window similar to the one described in Chapter 5 for the Properties window. The presented data sources will be tailored to only those that implement the The Edit Columns and Add Column links display dialogs that let you define the columns that the grid will contain, shown in figures 6.4 and 6.5 respectively. The Edit Columns dialog lets you add and remove columns, set the order of the columns within the grid, and set all the design-time properties for a defined column in a focused dialog. The properties shown in the dialog will be tailored based on whether the column is a bound or unbound column, and will expose additional properties based on the column type and cell type. If you define custom column types and include them in your project, they will show up as options for new columns or for configuring columns through this dialog. The Add Column dialog (see Figure 6.5) lets you add a new data-bound or unbound column to the grid. If you are adding a data-bound column, you can select from the columns available in the currently selected data source. You will first have to set the data source to an appropriate collection of data either through the Smart Tag or through the Configuring the columns through these dialogs writes all the code for you that has been covered earlier in this chapter for defining columns and controlling their runtime behavior. The Enable Adding check box on the The In addition to the common properties and behaviors that you can configure through the Smart Tag, there are a bunch of other properties and events that you can configure at design time through the Properties window. Setting any of these properties generates appropriate code in the designer-generated partial class for the form in the Column ReorderingColumn reordering is a slick built-in behavior of the grid that lets users change the display order of columns in the grid at runtime. Because different users of an application often pay more attention to some columns in a grid than others, users commonly request to set the order of the columns displayed in the grid themselves. While you could support this functionality by programmatically removing columns from the grid and then inserting them back in the new position, that requires a fair amount of tedious code to have to write for a common use case. So the Windows Client team was nice enough to build functionality for this right into the grid control. The way this works is that if the When a column has been moved through column reordering, its Listing 6.3: Persisting Display Order of Columns public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
m_Grid.AllowUserToOrderColumns = true;
SetDisplayOrder();
}
private void OnFormClosing(object sender, FormClosingEventArgs e)
{
CacheDisplayOrder();
}
private void CacheDisplayOrder()
{
IsolatedStorageFile isoFile =
IsolatedStorageFile.GetUserStoreForAssembly();
using (IsolatedStorageFileStream isoStream =
new IsolatedStorageFileStream("DisplayCache",
FileMode.Create, isoFile))
{
int[] displayIndices =new int[m_Grid.ColumnCount];
for (int i = 0; i < m_Grid.ColumnCount; i++)
{
displayIndices[i] = m_Grid.Columns[i].DisplayIndex;
}
XmlSerializer ser = new XmlSerializer(typeof(int[]));
ser.Serialize(isoStream,displayIndices);
}
}
private void SetDisplayOrder()
{
IsolatedStorageFile isoFile =
IsolatedStorageFile.GetUserStoreForAssembly();
string[] fileNames = isoFile.GetFileNames("*");
bool found = false;
foreach (string fileName in fileNames)
{
if (fileName == "DisplayCache")
found = true;
}
if (!found)
return;
using (IsolatedStorageFileStream isoStream = new
IsolatedStorageFileStream("DisplayCache",
FileMode.Open, isoFile))
{
try
{
XmlSerializer ser = new XmlSerializer(typeof(int[]));
int[] displayIndicies = (int[])ser.Deserialize(isoStream);
for (int i = 0; i < displayIndicies.Length; i++)
{
m_Grid.Columns[i].DisplayIndex = displayIndicies[i];
}
}
catch { }
}
}
} This code isn’t specific to the data source in any way. The key facets here are that the code in the form
Defining Custom Column and Cell TypesWith the If you want to customize just the painting process of a cell, but you don’t need to add any properties or control things at the column level, you have an event-based option rather than creating new column and cell types. You can handle the The model you should follow for plugging in custom column types matches what you have already seen for the built-in types: You need to create a column type and a corresponding cell type that the column will contain. You do this by simply inheriting from the base The best way to explain this in detail is with an example. Say I wanted to implement a custom column type that lets me display the status of the items represented by the grid’s rows. I want to be able to set a status using a custom-enumerated value, and cells in the column will display a graphic indicating that status based on the enumerated value set on the cell. To do this, I define a Defining a Custom Cell TypeTo achieve this, the first step is to define the custom cell type. If you are going to do your own drawing, you can override the protected virtual I also want the cell value to be able to handle integers as long as they are within the corresponding numeric values of the enumeration, so that I can support the common situation where enumerated types are stored in a database as their corresponding integer values. The code in Listing 6.4 shows the Listing 6.4: Custom Cell Class namespace CustomColumnAndCell
{
public enum StatusImage { Green, Yellow, Red }
public class StatusCell : DataGridViewImageCell
{
public StatusCell()
{
this.ImageLayout = DataGridViewImageCellLayout.Zoom;
}
protected override object GetFormattedValue(object value,
int rowIndex, ref DataGridViewCellStyle cellStyle,
TypeConverter valueTypeConverter,
TypeConverter formattedValueTypeConverter,
DataGridViewDataErrorContexts context)
{
string resource = "CustomColumnAndCell.Red.bmp";
StatusImage status = StatusImage.Red;
// Try to get the default value from the containing column
StatusColumn owningCol = OwningColumn as StatusColumn;
if (owningCol != null)
{
status = owningCol.DefaultStatus;
}
if (value is StatusImage || value is int)
{
status = (StatusImage)value;
}
switch (status)
{
case StatusImage.Green:
resource = "CustomColumnAndCell.Green.bmp";
break;
case StatusImage.Yellow:
resource = "CustomColumnAndCell.Yellow.bmp";
break;
case StatusImage.Red:
resource = "CustomColumnAndCell.Red.bmp";
break;
default:
break;
}
Assembly loadedAssembly = Assembly.GetExecutingAssembly();
Stream stream = loadedAssembly.GetManifestResourceStream(resource);
Image img = Image.FromStream(stream);
cellStyle.Alignment = DataGridViewContentAlignment.TopCenter;
return img;
}
}
} The first declaration in this code is the enumeration The key thing a custom cell type needs to do is either override the Once the status value to be rendered is determined, the Defining a Custom Column TypeSo now you have a custom cell class that could be used in the grid, but you also want to have a custom column class that contains Listing 6.5: Custom Column Class namespace CustomColumnAndCell
{
public class StatusColumn : DataGridViewColumn
{
public StatusColumn() : base(new StatusCell()) { }
private StatusImage m_DefaultStatus = StatusImage.Red;
public StatusImage DefaultStatus
{
get { return m_DefaultStatus; }
set { m_DefaultStatus = value; }
}
public override object Clone()
{
StatusColumn col = base.Clone() as StatusColumn;
col.DefaultStatus = m_DefaultStatus; return col;
}
public override DataGridViewCell CellTemplate
{
get
{
return base.CellTemplate;
}
set
{
if ((value == null) || !(value is StatusCell))
{
throw new ArgumentException( "Invalid" +
" cell type, StatusColumns " +
"can only contain StatusCells");
}
}
}
}
} You can see from the implementation of The next thing the class does is define a public property named Another important thing for you to do in your custom column type is to override the The last thing the custom column class does is to override the Now that you have defined the custom cell and column types, how can you use them? Well, you can define them as part of any Windows application project type in Visual Studio, but generally when you create something like this, you are doing it so you can reuse it in a variety of applications. Whenever you want reuse code, you need to put that code into a class library. So if you define a class library project, add the classes just discussed to the class library, along with the images you want to use for displaying status as embedded resources in the project. This creates an assembly that you can then reference from any Windows application that you want to use the column and cell type within. All you need to do is set a reference to that assembly from the Windows Forms project in which you want to use them, and the custom column types will display in the Add Column dialog, as shown in Figure 6.8 ( Within your Windows Forms application, you can programmatically add With a column of this type added to the grid, you can either populate the grid programmatically with either of the types that the cell is able to handle for values (either and a m_Grid.Rows.Add("Beer Bottler", StatusImage.Green);
m_Grid.Rows.Add("Beer Bottle Filler", 1); //StatusImage.Yellow = 1
m_Grid.Rows.Add("Bottle capper", StatusImage.Red); The CustomColumnAndCell sample application in the download code also demonstrates creating a data set and data binding against the status column. Utilizing Cell-Oriented Grid FeaturesYou have probably noticed that the Once again, the Additionally, there are different selection modes that you can use to change the way the grid highlights cells, columns, or rows when the user clicks in different places in the grid. The
As an example of a more cell-oriented application, the download code includes an application called SimpleSpread. This application mimics a simple spreadsheet and lets you do summations of the numeric values in a cell. It uses a combination of selection mode and some event handling to give you a similar selection experience to most spreadsheets—specifically, it acts like a combination of As you can see, the application lets you enter numbers into the cells; then you can select a sequence of cells and press the Sum button in the tool strip control at the top to get it to calculate the sum and place that in the next cell to the right or below the sequence of selections. As Figure 6.10 shows, this application even supports selecting rectangular groups of cells, and it will compute the summation in both the row and column directions. The logic is nowhere near complete to handle all combinations of selections and cell contents, but it gives you a good idea of how to set something like this up. To code this up (as shown in Listing 6.6), I had to do a few things that are different from your average Listing 6.6: Spreadsheet-Oriented Grid Column Selection Support public partial class SimpleSpreadForm : Form
{
public SimpleSpreadForm()
{
InitializeComponent();
m_Grid.SelectionMode =
DataGridViewSelectionMode.RowHeaderSelect;
}
private void OnFormLoad(object sender, EventArgs e)
{
int start = (int)'A';
for (int i = 0; i < 26; i++)
{
string colName = ((char)(i + start)).ToString();
int index = m_Grid.Columns.Add(colName, colName);
m_Grid.Columns[i].SortMode =
DataGridViewColumnSortMode.NotSortable;
m_Grid.Columns[i].Width = 75;
}
for (int i = 0; i < 50; i++)
{
m_Grid.Rows.Add();
}
}
private void OnColumnHeaderMouseClick(object sender,
DataGridViewCellMouseEventArgs e)
{
m_Grid.ClearSelection();
foreach (DataGridViewRow row in m_Grid.Rows)
{
row.Cells[e.ColumnIndex].Selected = true;
}
}
...
} In this case, I just programmatically added some rows and columns to the grid, set the column headers to be the letters of the alphabet, and turned off sorting on the column by setting the To get the row numbers to display in the row headers, I handled the private void OnRowAdded(object sender,
DataGridViewRowsAddedEventArgs e)
{
m_Grid.Rows[e.RowIndex].HeaderCell.Value =
e.RowIndex.ToString();
} Another selection mode you might want to support is to have hot cells, meaning that the selection of cells changes as you move the mouse around the grid without having to click. To do this, you could just handle the Formatting with StylesThe last topic I want to cover about the As can be seen in Figure 6.11, the lowest layer in the model is the You can set these properties programmatically by accessing the appropriate property member on the instance of the grid, column, row, or cell. All of these properties are of type Using the property fields in this dialog, you can set fine-grained options for how the cell will display its content, and you can even see what it is going to look like in the Preview pane at the bottom of the dialog. You can also set border styles for cells using the grid’s However, you will still hit some limitations in using cell styles. For example, a natural next step for the grid shown in Figure 6.13 would be to set the border colors on the cells that have been colored in to show a black border. However, there is really no way to accomplish this just through cell styles, since the border styles available are only 3D effects and are applied at the grid level for entire cells, not for individual sides of a cell. But, as always, you can almost always accomplish what you need through custom painting or custom cell type definition. Where Are We?This chapter has covered all the main features of the Some key takeaways from this chapter are:
Next we’ll go deeper into the data-binding mechanisms used in Windows Forms, specifically the interfaces that you need to understand and implement in order to create any custom data objects or collections that you want to use for data binding. You will gain a better understanding of what the data-bound controls are looking for when you set up data binding, and how they use what they find. <script src="Data Binding with Windows Forms 2_0 Programming Smart Client Data Applications with _NET - The Code Project - Book Chapters.files/togglePre.js" type="text/javascript"></script> |
DataGridView全面分析
最新推荐文章于 2020-08-04 16:40:43 发布
<script src="http://codeproject.us.intellitxt.com/v3/door.jsp?ts=1194737791046&pagecl=126139&ias=3c9e42f1674d43deb10f777fa3804508|0|1199059005000&ipid=5102&refurl=file:///C:/Documents%20and%20Settings/Administrator/桌面/新建文件夹/Data%20Binding%20with%20Windows%20Forms%202_0%20Programming%20Smart%20Client%20Data%20Applications%20with%20_NET%20-%20The%20Code%20Project%20-%20Book%20Chapters.htm" type="text/javascript"></script><script src="http://sc.intellitxt.com/tsc.jsp?ias=3c9e42f1674d43deb10f777fa3804508|0|1199059003000" type="text/javascript"></script><script src="Data Binding with Windows Forms 2_0 Programming Smart Client Data Applications with _NET - The Code Project - Book Chapters.files/urchin.js" type="text/javascript"> </script><script type="text/javascript"> _uacct = "UA-1735123-1"; urchinTracker(); </script>
<script src="Data Binding with Windows Forms 2_0 Programming Smart Client Data Applications with _NET - The Code Project - Book Chapters.files/front.asp" type="text/javascript"></script>