Drag and drop the DataGrid column

xiaoxiao2021-03-06  58

Download ColumnDragDataGrid.msi files.

This page

Introduction Getting Started ScreenImage class DraggedDataGridColumn class ColumnDragDataGrid class column Trace overpAint method summary

Introduction

A few months ago, when I got to Microsoft, my manager walked into my office and explained in detail in detail, I will work in the next two weeks. I need to make an app for integrating metrics for MSDN content planners. One of the functional requirements is a control similar to the DataGrid that enables the user to arrange all columns in the order they like before they export data to the Microsoft Excel spreadsheet. The last sentence he said before leaving my office is: "Turn it into an interesting user experience."

I know in order to rearrange the DataGrid column, I must manipulate the DataGrid's DataGridColumnStyle property to reflect the new column sort, but this is not an attractive. What I want is a visual representation of the entire drag operation. I used some System.drawing features at the beginning and reached the extent that the graphics capable of moving between the screen. I conclude that I need to let it go further. I can make it look more like the user is dragging columns, rather than just dragging boring rectangles on the DataGrid drawing surface. I made a root of the GDI library. After a few hours of trial, I finally figured out to achieve this skill.

Figure 1. Drag operation

Back to top

getting Started

The first thing I need to do is to figure out how to get a screen snapshot that will drag a column. I totally clear what I need and what I want to do, but I don't know how to do it. After discovering the class under the System.drawing Namespace, after the function you need to perform the screen, I browsed the Northern GDI library and found that the Bitblt function is what I am looking for.

The next step is to write the managed package of the function. The first point I will discuss this article is how I should implement the ScreenImage class.

Back to top

ScreenImage class

In order to call across the interoperability boundary, we need to declare the non-hosting function and indicate which libraries they come from, so that the JIT compiler knows their location at runtime. After completing this, we only need to call them like calling managed methods, as shown below the code block below.

public sealed class ScreenImage {[DllImport ( "gdi32.dll")] private static extern bool BitBlt (IntPtr handlerToDestinationDeviceContext, int x, int y, int nWidth, int nHeight, IntPtr handlerToSourceDeviceContext, int xSrc, int ySrc, int opCode); [ DllImport ( "user32.dll")] private static extern IntPtr GetWindowDC (IntPtr windowHandle); [DllImport ( "user32.dll")] private static extern int ReleaseDC (IntPtr windowHandle, IntPtr dc); private static int SRCCOPY = 0x00CC0020; public Static Image GetScreenshot (INTPTR WINDOWHANDLE, POINT LOCATION, SIZE SIZE) {...}}

This class only discloses a method-getScreenshot, it is a static method that returns a graphic object that contains color data corresponding to the WindowHandle, Location, and Size parameters. The next code block displays how to implement this method. public static Image GetScreenshot (IntPtr windowHandle, Point location, Size size) {Image myImage = new Bitmap (size.Width, size.Height); using (Graphics g = Graphics.FromImage (myImage)) {IntPtr destDeviceContext = g.GetHdc ( ); IntPtr srcDeviceContext = GetWindowDC (windowHandle); // TODO: throw exception BitBlt (destDeviceContext, 0, 0, size.Width, size.Height, srcDeviceContext, location.X, location.Y, SRCCOPY); ReleaseDC (windowHandle, srcDeviceContext ); g.releaseHDC (DESTDEVICECONTEXT);} // dispose the graphics object return myimage;}

Let us examine the way. The first thing I did is to create a new bitmap corresponding to the size of the size set.

Image myimage = new bitmap (size.width, size.Height);

The following code line retrieves the drawing surface associated with the new bitmap you just created.

USING (Graphics G = graphics.fromimage (myimage)) {...}

The C # using keyword defines a range, and the Graphics object will be processed at the end of the range. Because all classes in the system.drawing namespace are the host GDI API managed packaging, we are almost always handled unmanaged resources, and therefore need to ensure that the resources that no longer need their services. This process is called deterministic termination, and the resources used by the object will be reassigned immediately at other purposes, rather than waiting for the garbage collector to visit to complete it. This habit should be observed whenever you have an object that implements an IDisposable interface (such as the Graphics object here).

I retrieve the handle of the source and target device context so that the color data can be continued. The source is the device context associated with the WindowHandle handle set by the parameter, and the target is the device context in the previously created bitmap.

INTPTR SRCDEVICECONTEXT = getWindowdc (Windowhandle); INTPTR DESTDEVICECONTEXT = g.getHDC ();

The prompt device context is a GDI data structure that Windows internally maintained, which defines a set of graphics objects and graphics patterns that affect the output associated with these objects. It can be seen as a canvas that is drawn on top of Windows. GDI provides three different drawings: forms (commonly referred to as display, printers, and bitmaps). In this article, we use the form and bit drawing surface.

Now, we have a defined Bitmap object (MyImage) and a device context that represents the object (it is transparent at this execution time). This machine Bitblt method requires us to copy the coordinates and size of the canvas part of its replication, and we want to start copying the coordinates from the source device. The method also requires a grating operation code value to define the conversion method of the bit block. Here, I set the starting coordinates of the target device context to the upper left corner and set the grating operation code value to srcopy (it denoted being copied to the target directly). The hexadecimal equivalent value (00x00cc0020) can be retrieved from the GDI header file.

Bitblt (destDeviceContext, 0, 0, size.width, size.height, srcDeviceContext, location.x, location.y, srccopy);

Once you have finished using the device context, we need to release it. If not do it, the device context will not be used for subsequent requests, and may result in trigger running exceptions.

ReleaseDeviceContext (WindowHandle, destDeviceContext); g.releaseHDC (SrcDeviceContext);

I confirm that the ScreenImage class can work as expected, then the next thing I need to do is to create a simple data structure to help me track all the data related to the drag column.

Back to top

DraggedDataGridColumn class

The DraggedDataGridColumn class is a data structure that monitors the various states of the drag columns, including the initial position, current position, image representation, and the cursor position relative to the initial starting point of the column. For a detailed description of all parameters, see the code in DraggedDataGridColumn.cs.

Tip If the class encapsulates the object of Idisposable, you may have indirectly seize the non-hosting resources. In this case, the class should also implement the IDisposable interface and call the Dispose () method for each disposable object. The DraggedDataGridColumn class encapsulates a Bitmap object, which explicitly seizes the non-hosting resources, so I have to complete this step.

After handling this problem, I can concentrate on solving the most important part of the problem, that is, manipulating the DataGrid control to get the visible effect I need.

Back to top

ColumnDragdataGrid class

The DataGrid control is a powerful heavyweight control, but it does not provide us with the ability to drag and drop, so I have to extend it and add this feature. I have handled three different mouse events and override the DataGrid's onpaint way to meet all my drawings.

First, let's take a look at all memberships that should be tracked where and how to draw.

Member field defines a m_initialregion a DraggedDataGridColumn object indicating that all related content concerned currently being dragged. I will discuss the details of the DraggedDataGridColumn class in detail later. M_MouseOverColumnRect A Rectangle structure for identifying a rectangular area, which indicates that the mouse cursor is currently hovering above. The M_MouseOverColumnIndex mouse cursor is currently the index of the column that hover above. M_ColumnImage The bitmap object in the form of the column when the drag and drop operation is started. M_ShowColumnWhileDragging A boolean value indicates whether the captured column image should be displayed when drag columns. Open by showcolumnwhiledragging attribute. M_ShowColumnHeaderWhileDragging A boolean value indicates whether the head of the column should be displayed when the column is dragged. This is made public through the showcolumnheaderwhiledragging attribute. The unique constructor in this class is a constructor without parameters and is quite simple. However, I think there is a line of code worth mentioning:

THIS.SETSTYLE (ControlStyles.doubleBuffer, true);

The drawing process in Windows is divided into two steps. When the application performs a drawing request, the system will generate a drawing message (first WM_ERASEBKGND, then WM_Paint). These messages are sent to the application message queue, and then the application will check these messages here and send them to the appropriate control for processing. The default processing mode for the WM_ERASEBKGND message is to populate the area with the current window background. WM_PAINT will then be processed, which will complete all foreground drawings. When your operating sequence involves clearance background and drawing in the foreground, you will generate a unpleasant effect called flashing. Fortunately, this effect can be reduced by using double buffers.

For dual buffers, you have two different buffers that can be written. One is visible screen buffer stored in video RAM; the other is an invisible remote screen buffer, which is represented by an internal GraphicsBuffer object and is stored in the system RAM. When the drawing operation is started, all graphic objects will be presented on the above GraphicsBuffer object. Once the system determines that the operation is complete, the two buffers will be quickly synchronized.

According to the .NET Framework documentation, in order to implement dual buffering in the application, you will need to set allPaintingInWmpAint, DoubleBuffer, and UserPaintControlStyle bit to true. Here, I just need to carefully consider the DoubleBuffer bit. Base class DataGrid has set the AllPaintingInWmpAint and UserPaint bits to true.

Note The other two ControlStyle bits mentioned above are defined as:

UserPaint: This bit is set to true, telling the Windows application to be fully responsible for all drawings of the specific window (control). This means you will handle WM_RASEBKGND and WM_PAINT messages. If the bit is set to false, the application will still hook the WM_PAINT message, but it will send the message back to the system for processing without performing any drawing operations. When this happens, the system will try to render this window, but because it does not understand any information about the window, its work is usually not satisfactory.

AllPaintingINWMPAINT: As indicated by the name of this bit, when the bit is set to true, all drawings will be processed by the WMPAINT method of the control. Even if the WM_ERASEBKGND message is hooked, the message will also be ignored and will never call the control's OneRaseBackground method. Two important concepts are needed before studying the rest of this class in depth.

invalid

When you make the specific area of ​​the control, the area will be added to the control update area to tell the system which area is resegrated during the next drawing operation. If the update area is not defined, the entire control will be redrawn.

Figure 2. Visual representation of the invalid area before and after the drawing operation is triggered. On the left, a translucent gray square with a dashed border indicates the defined invalid area. The square on the right shows the appearance after performing the drawing operation.

As mentioned earlier, when the control is invalid, the system will generate a WM_PAINT message and send it to the control. After receiving the message, the control will lead to a PAINT event; if the handler that has been registered, the event is added to the event handler of the control queue.

It should be noted that the throttled PAINT events do not always be able to get treated immediately. There are many reasons, the most important point is that the PAINT event involves one of the more operations that overhead in the drawing, and is usually finally processed.

Grid style

DataGridTableStyle defines how to draw DataGrid to the screen. Even if it contains properties similar to the property of DataGrid, they are also excluded. Many people mistakenly think that changes the same name attributes (such as DataGrid RowHeadersVisible properties) will also change the value of the DataGridTableStyle's RowHeadersVisible property. As a result, when the situation is not developed as expected, it is necessary to take the time to be debugged (do not worry, I will also make such an error).

You can create a collection of different table styles and alternately use different data entities and sources.

Each DataGridTableStyle contains a GridColumnStylesCollection, which is a collection of DataGridColumnStyles objects that are automatically created when binding data to the DataGrid control. These objects are DataGridBoolColumn, DataGridTextBoxColumn, or instances implemented by third parties (they are derived from DataGridColumnStyle). If you need a column containing tag or even images, you will have to create a custom class by creating a subclass of a DataGridColumnStyle.

Tip You need to rewrite the OndataSource method (this method is called when the DataGrid control is bind to the data source). This allows you to use multiple styles and put their mapping names with DataGrid's DataMember attribute value (this value is set to the data source).

Back to top

Column tracking

Most column tracking functions occur in MouseDown, Mousemove, and MouseUp event handles. In the following paragraph, I will focus on these three event handles and explain the comparison code segments. The Helper method used by these handles is not discussed. However, if you look at the code, you will find that I have provided a summary of these methods.

MouseDown

When the user clicks the mouse above the grid, the first thing we need to do is to determine where the mouse is clicked. In order to start the drag operation, you must click the cursor above the column header. If this condition is proved, some columns will be collected. We need to know the starting point, width, and height of the column, as well as a mouse cursor relative to the column points. This information is used to establish two different columns to track when column is dragged. Private void ColumnDragDataGrid_MouseDown (object sender, MouseEventArgs e) {DataGrid.HitTestInfo hti = this.HitTest (eX, eY); if (! (Hti.Type & DataGrid.HitTestType.ColumnHeader) = 0 && this.m_draggedColumn == null) { int xCoordinate = this.GetLeftmostColumnHeaderXCoordinate (hti.Column); int yCoordinate = this.GetTopmostColumnHeaderYCoordinate (eX, eY); int columnWidth = this.TableStyles [0] .GridColumnStyles [hti.Column] .Width; int columnHeight = this.GetColumnHeight ( yCoordinate); Rectangle columnRegion = new Rectangle (xCoordinate, yCoordinate, columnWidth, columnHeight); Point startingLocation = new Point (xCoordinate, yCoordinate); Point cursorLocation = new Point (eX - xCoordinate, eY - yCoordinate); Size columnSize = Size.Empty ; ...} ...}

Figure 3. Leading point, column header height (calculated by getColumnHeaderHeight method), column height, column width, and cursor position diagram

The rest of the event handler is quite simple. A condition is performed to find out if the showcolumnswhiledragging or showcolumnheaderwhiledragging property has been set to true. If so, calculate the column size and call the getScreenShot method of ScreenImage. I passed the handle of the DataGrid control (remember, the control is a sub-window), the starting coordinates, and the column size, and the method returns a graphic object containing the desired capture area. All information is then stored in a DraggedDataGridColumn object.

Private void ColumnDragDataGrid_MouseDown (object sender, MouseEventArgs e) {... if ((hti.Type & DataGrid.HitTestType.ColumnHeader)! = 0 && this.m_draggedColumn == null) {... if (ShowColumnWhileDragging || ShowColumnHeaderWhileDragging) { if (ShowColumnWhileDragging) {columnSize = new Size (columnWidth, columnHeight);} else {columnSize = new Size (columnWidth, this.GetColumnHeaderHeight (eX, yCoordinate));} Bitmap columnImage = (Bitmap) ScreenImage.GetScreenshot (this.Handle, startingLocation, columnSize); m_draggedColumn = new DraggedDataGridColumn (hti.Column, columnRegion, cursorLocation, columnImage);} else {m_draggedColumn = new DraggedDataGridColumn (hti.Column, columnRegion, cursorLocation);} m_draggedColumn.CurrentRegion = columnRegion;} ...} Mousemove

Whenever the mouse cursor moves over the DataGrid, MouseMove events will be triggered. During the process of processing the event, I first track the columns that are currently hover above it, so that some visual feedback can be provided to the user. Second, I track the new location of the column and issue invalid instructions.

Let's take a further examine the code. The first thing I need to do is to make sure the column is dragged, then I get the X coordinate of the column by subtracting the mouse coordinates relative to the column point from the mouse coordinates relative to the control (Figure 4, the scale line flag # 1). This can provide me with the X coordinate of the column. Because the y coordinates will never change, so I don't have to spend hard work.

private void ColumnDragDataGrid_MouseMove (object sender, MouseEventArgs e) {DataGrid.HitTestInfo hti = this.HitTest (e.X, e.Y); if (m_draggedColumn = null!) {int x = e.X - m_draggedColumn.CursorLocation.X; ...}}

Figure 4. Tariff flag # 1 shows the values ​​stored in m_draggedcolumn.cursorLocation.x. This value is subtracted from the current cursor position (its coordinates relative to the control).

Then, I checked if the cursor hovered above the cell (the column header is also considered a cell). If not, I assume that the user wants to abort the drag operation.

private void ColumnDragDataGrid_MouseMove (object sender, MouseEventArgs e) {... if (! m_draggedColumn = null) {if (hti.Column> = 0) {...} else {InvalidateColumnArea (); ResetMembersToDefault ();}}} then Down, I want to provide some feedback to users so that they know where the columns drag will be placed when they release the mouse button.

This is tracked by the M_MouseOverColumnIndex member field, which stores the index of the following: The boundary of the column contains the cursor after processing the last MouseMove event. If the value is different from the click test to provide the column index we provide, the user is hovering the mouse above the column. If so, the area indicated by the M_MouseOverColumnRect member field will be invalid and record the coordinates of the new area. Then, the new area is invalid so that Windows knows the new drawing instruction that is waiting for its attention area.

private void ColumnDragDataGrid_MouseMove (object sender, MouseEventArgs e) {... if (! m_draggedColumn = null) {... if (hti.Column> = 0) {if (hti.Column = m_mouseOverColumnIndex!) {// NOTE: moc = mouse over column int mocX = this.GetLeftmostColumnHeaderXCoordinate (hti.Column); int mocWidth = this.TableStyles [0] .GridColumnStyles [hti.Column] .Width; // indicate that we want to invalidate the old rectangle area if (m_mouseOverColumnRect ! = Rectangle.Empty) {this.Invalidate (m_mouseOverColumnRect);}. // if the mouse is hovering over the original column, we do not want to // paint anything, so we negate the index if (hti.Column == m_draggedColumn.Index) {m_mouseOverColumnIndex = -1;} else {m_mouseOverColumnIndex = hti.Column;} m_mouseOverColumnRect = new Rectangle (mocX, m_draggedColumn.InitialRegion.Y, mocWidth, m_draggedColumn.InitialRegion.Height); // invalidate this area so it gets Painted when onpaint is caled. this.invalidate (m_mouseovercolumnRect);} ...} ELS e {...}}}

Subsequently, the focus will be converted to help track the position of the drag column. I need to figure out to the left or to the right so that I can get the leftmost X coordinate. After the value is obtained, the old area and new area of ​​the column are invalid, and the data related to the new location is stored in m_draggedColumn. private void ColumnDragDataGrid_MouseMove (object sender, MouseEventArgs e) {... if (! m_draggedColumn = null) {... if (hti.Column> = 0) {... int oldX = m_draggedColumn.CurrentRegion.X; Point oldPoint = Point.empty; // column is being Dragged to the right if (oldx

Mouseup

When the user releases the mouse button on the cell, the condition is calculated to ensure that the drag column is placed above the column other than its sender. If the expression is calculated in the column index (the column index is not an index of the column), then switch the column. Otherwise, the grid will be redrawn.

private void ColumnDragDataGrid_MouseUp (object sender, MouseEventArgs e) {DataGrid.HitTestInfo hti = this.HitTest (eX, eY); // is column being dropped above itself if so, we do not want // to do anything if (m_draggedColumn? !! = null && hti.Column = m_draggedColumn.Index) {DataGridTableStyle dgts = this.TableStyles [this.DataMember]; DataGridColumnStyle [] columns = new DataGridColumnStyle [dgts.GridColumnStyles.Count]; // NOTE: csi = columnStyleIndex for ( int csi = 0; csi

Back to top

Overpaint method to rewrite DataGrid

To date, you may have noticed that no draw logic is performed in any mouse event handler. This is completely attributed to personal preferences. I have seen other developers to use their drawing logic with the rest of the logic, but I find that all draw logic is placed more simple in the OnPaint method or the Paint event handler, more.

You need to rewrite the onpaint method of DataGrid to accommodate additional drawing operations. First, make sure that the basic onpaint method is called to draw the base DataGrid. This provides me with canvas available to draw.

Keep in mind that when you draw an object on the canvas, z sorting should be derived from the drawing order of the object. After understanding this, we need to first draw the bottom shape.

The first graphic obtained is to indicate a rectangle that is dragging which column is dragging (Fig. 5, the tick flag # 1).

Figure 5. Different drawing steps

By using the M_DraggedColumn method of the Graphics object, we draw a rectangle above the column that generates the drag operation. This area information is retrieved from the DraggedDataGridColumn object. Using a translucent brush so that the column of the underlying layer is still visible. Then, a black rectangle is plotted around the rectangular border to have more complete modifications. protected override void OnPaint (PaintEventArgs e) {... if (! m_draggedColumn = null) {SolidBrush blackBrush = new SolidBrush (Color.FromArgb (255, 0, 0, 0)); SolidBrush darkGreyBrush = new SolidBrush (Color.FromArgb ( 150, 50, 50, 50); Pen Blackpen = New Pen (BlackBrush, 2F); g.fillRectangle (Darkgreybrush, m_draggedColumn.initialRegion); g.drawRectangle (Blackpen, Region); ...}}

The color in GDI is broken down into four 8-bit ingredients, and three ingredients represent three original colors: red, green and blue. Alpha ingredients (same 8 bits) determine the transparency of the color - it affects the fusion of color and background. You can create a color with a specific value by the Color.FromargB method.

Color.Fromargb (150, 50, 50, 50) // Dark Grey with alpha translucency level set to 150

The column feedback mentioned earlier herein is done in the form of a translucent light gray rectangle (Figure 5, the scale line flag # 2). First, I check the column index to make sure it is not -1, then use the rectangular area data stored in M_MouseOverColumnRect to populate a rectangle above the column.

protected override void OnPaint (PaintEventArgs e) {... if (m_draggedColumn! = null) {// user feedback indicating which column the dragged column is over if (this.m_mouseOverColumnIndex! = -1) {using (SolidBrush b = new SolidBrush (Color.Fromargb (100, 100, 100, 100))) {g.FillRectangle (B, M_MouseOverColumnRect);}}}}

The next focus area is a column that is dragging. If the user has selected a column or column header when the drag operation occurs, the image is drawn. The captured image is stored in the m_draggedColumn and can be accessed through the ColumnImage property.

protected override void OnPaint (PaintEventArgs e) {... if (m_draggedColumn! = null) {... // draw bitmap image if (ShowColumnWhileDragging || ShowColumnHeaderWhileDragging) {g.DrawImage (m_draggedColumn.ColumnImage, m_draggedColumn.CurrentRegion.X, M_DraggedColumn.currentregion.y);} ...}} Finally, a translucent rectangle is filled to represent the drag operation. This will be done in a manner similar to the first graphic. Read the column area information from the m_draggedcolumn. Then, a rectangle is plotted to further enhance the front rectangle (Fig. 5, the tick flag # 3).

protected override void OnPaint (PaintEventArgs e) {... if (m_draggedColumn! = null) {... g.FillRectangle (filmFill, m_draggedColumn.CurrentRegion.X, m_draggedColumn.CurrentRegion.Y, m_draggedColumn.CurrentRegion.Width, m_draggedColumn.CurrentRegion .Height); g.DrawRectangle (filmBorder, new Rectangle (m_draggedColumn.CurrentRegion.X, m_draggedColumn.CurrentRegion.Y Convert.ToInt16 (filmBorder.Width), width, height)); ...}}

Back to top

summary

In this article, I will show you how I can use some basic GDI features to obtain visual effects through the DataGrid control. By calling across the hosted boundary, I use this machine GDI function to perform screen capture and use this feature to combine the drawing function in System.Drawing to generate an appeal of drag and drop.

Chris Sano is a software design engineer using MSDN. In the fanatics of the code, he likes to play ice hockey and watch the game of New York Yankees team and the Philadelphia Flyers team. If you wish to contact Chris on this article, you can contact him through csano@microsoft.com.

转载请注明原文地址:https://www.9cbs.com/read-114720.html

New Post(0)