I need to merge two column headers that are side-by-side into a single column header. How do I do that? I'd like the columns themselves to remain separate. I just want the header to look like a single header.
Hi,
There's no way to do this via simple property settings.
But there are a couple of ways you can achieve what you want. It's going to depend on what version of the controls you are using, though.
If you have a pretty recent version, then you could set the RowLayoutStyle on your grid to GroupLayout. Then you put the two columns into a group and hide the columns headers so that the group header takes the place of the column headers.
If you have an old version of the controls, then you may not have the option for GroupLayout, so you could use a RowLayout with an unbound column. You use the column.RowLayoutColumnInfo.LabelPosition to hide the column headers for the two real bound columns and hide the cells (LabelOnly) for the unbound column. The unbound column's header will act as the combined header for the two real columns.
A third option, which will work in any version of the grid but is a bit harder to implement it to use a CreationFilter to remove the UIElement for one of the column headers and extend the other into the empty space thus created.
This will be tricky, though, especially if you are not experienced with CreationFilters.
Mike, I'm pursuing the CreationFilter method, but I'm a bit confused. The code is below.
My problem is that BeforeCreateChildElements() gets called multiple times for the same headers without resetting the header rectangle to its original values and because of this, the code that calculates the rect, by adding the width of the adjacent column, continues to add that width over and over and over again, resulting in a column header that's ridiculously wide.
I can't seem to determine when it's getting called for the first time vs. the 5th time. How can I fix it so it only gets called once, or at least, only calculates the new header width once.
Thanks
public class ColHeaderSpannerCreationFilter : IUIElementCreationFilter { private string ColumnKeyMain { get; set; } private string ColumnKeyHidden { get; set; } private UltraGrid Grid { get; set; } public ColHeaderSpannerCreationFilter(UltraGrid grid, string colKeyMain, string colKeyHidden) { Grid = grid; ColumnKeyMain = colKeyMain; ColumnKeyHidden = colKeyHidden; } public void AfterCreateChildElements(UIElement parent) { } public bool BeforeCreateChildElements(UIElement parent) { if (parent is HeaderUIElement) { //Get a reference to the cell, and the cells ui element. HeaderUIElement headerUIElement = (HeaderUIElement)parent; UltraGridColumn headerCell = headerUIElement.Header.Column; // Left-hand column if (headerCell.Key == ColumnKeyMain) { UltraGridColumn rightCol = Grid.DisplayLayout.Bands[0].Columns[ColumnKeyHidden]; //This is a cell that should span two columns, so we stretch its rectangle. headerUIElement.Rect = new Rectangle(headerUIElement.Rect.X, headerUIElement.Rect.Y, headerUIElement.Rect.Width + rightCol.Width, headerUIElement.Rect.Height); return true; } // Right-hand column if (headerCell.Key == ColumnKeyHidden) { //Here we are hiding the cell by shrinking its height and width down to 0. headerUIElement.Rect = new Rectangle(headerUIElement.Rect.X, headerUIElement.Rect.Y, 0, 0); //Return true to let the grid know that we handled the event. return true; } } //Return false to let the grid know we did not handle the event. return false; } }
The element should always have it's Rect set before the BeforeCreateChildElements fires. But I don't think the approach you took here is quite right, anyway. It's probably not a good idea to move the parent element.
Your code is also making the assumption that you wlil always be using the main column's HeaderUIElement, but that will not work if that column is scrolled out of view.
Here's a better way:
public class ColHeaderSpannerCreationFilter : IUIElementCreationFilter { private UltraGridColumn Column1 { get; set; } private UltraGridColumn Column2 { get; set; } public ColHeaderSpannerCreationFilter(UltraGridColumn column1, UltraGridColumn column2) { this.Column1 = column1; this.Column2 = column2; } public void AfterCreateChildElements(UIElement parent) { if (parent is BandHeadersUIElement) { HeaderUIElement headerElement1 = parent.GetDescendant(typeof(HeaderUIElement), this.Column1) as HeaderUIElement; HeaderUIElement headerElement2 = parent.GetDescendant(typeof(HeaderUIElement), this.Column2) as HeaderUIElement; // If boith of the elements are scrolled out of view, then do nothing. if (headerElement1 == null && headerElement2 == null) { return; } UIElement elementToExtend = headerElement1 != null ? headerElement1 : headerElement2; UIElement elementToRemove = headerElement1 != null ? headerElement2 : headerElement1; // Make one header element fill the space of both column headers. if (elementToRemove != null) { elementToExtend.Rect = Rectangle.Union(elementToExtend.Rect, elementToRemove.Rect); // Remove the other element parent.ChildElements.Remove(elementToRemove); } } } public bool BeforeCreateChildElements(UIElement parent) { //Return false to let the grid know we did not handle the event. return false; } }
This works pretty well, but there is a small problem when you scroll the column(s) out of view and then back into view, because the grid doesn't know it has to dirty both header elements. So you just have to tell the grid to dirty the headers when it scrolls. Like so:
private void ultraGrid1_BeforeColRegionScroll(object sender, BeforeColRegionScrollEventArgs e) { UltraGrid grid = (UltraGrid)sender; UIElement bandHeadersUIElement = grid.DisplayLayout.UIElement.GetDescendant(typeof(BandHeadersUIElement)); if (bandHeadersUIElement != null) bandHeadersUIElement.DirtyChildElements(true); }
Can you provide any sample code to handle this scnerios?
Ahhh, okay, I see it now. I misunderstood what you wrote.
"When we move any other column(except the 2 columns whose headers we are merging) to left of 1st column(whose header is removed and 2nd header is extended), then extended header further increases its width to include other moved column header as well."
I thought you meant drag a column and drop it in front of the merged column. But you mean drag a column that is already in front of the merged column and drop it into a position after the merged column.
The problem here is that the grid determines where to place the dropped column using the UIElements, and the UIElement in this case is the UIElement for Column 1. In fact, it depends exactly where you place the mouse. If you drop when the mouse pointer is over Column 3, then it works fine, because the column is placed before Column 3. If the mouse is over the header for Column 1, then it breaks because you are dropping after Column 1 (in between Column 1 and Column 2).
When I posted this CreationFilter, I never really intended it to work with column moving. These columns appear stuck together, but they are not really joined in any way, so the grid cannot keep them together when moving columns around.
I think I was correct in my previous reply - if you want to be able to suppose column moving, then you will need to handle the BeforeColPosChanged and/or AfterColPosChanged event and fix-up any problems that may occur because the two joined columns got separated. This is not trivial undertaking because you will have to deal with all sorts of cases.
Dropping after Column 1 is one case.
Moving Column 1 itself is another.
And the user can drag more than one column at a time.
PFA video to reproduce the scenario.
I'm still not seeing anything wrong here. Here's what I did:
1) Run the sample.
2) Click on the column header for Column 3 to select the column.
3) Click and drag the column header for Column 3 and drop it before Column1.
I then see Column 0, Column 3, Column 1 (which is still the width of 2 columns), Column 4, etc.
Are you seeing something else?
I don't see how ColSwapping being on could possibly have any effect here, but I also tried swapping Column 3 with Column 1 and it still works just fine for me.
I do see a problem if you swap Column 3 with Column 2. Is that what you are doing? Clearly, this would not make sense, because this means you end up moving Column 2 so it is no longer next to Column 1. But that's not what you described here.
Using ColSwapping in this kind of scenario is probably a bad idea. But if you must have ColSwapping, the only thing I can think of is that you could handle the BeforeColPosChanged event and cancel any operation that results in Column 2 no longer being next to Column 1. This is probably not a bad idea to do anyway, just in case.
Hi Mike,
Same behviour can also be checked without enabling column swapping(Please check with sample attached in last post). Just try to move columns to the left of mergerd columns.