WPF FlowDocument: force calculation of height etc. "off screen" - wpf

My target: a DocumentPaginator which takes a FlowDocument with a table, which splits the table to fit the pagesize and repeat the header/footer (special tagged TableRowGroups) on every page.
For splitting the table I have to know the heights of its rows.
While building the FlowDocument-table by code, the height/width of the TableRows are 0 (of course). If I assign this document to a FlowDocumentScrollViewer (PageSize is set), the heights etc. are calculated. Is this possible without using an UI-bound object? Instantiating a FlowDocumentScrollViewer which is not bound to a window doesn't force the pagination/calculation of the heights.
This is how I determine the height of a TableRow (which works perfectly for documents shown by a FlowDocumentScrollViewer):
FlowDocument doc = BuildNewDocument();
// what is the scrollviewer doing with the FlowDocument?
FlowDocumentScrollViewer dv = new FlowDocumentScrollViewer();
dv.Document = doc;
dv.Arrange(new Rect(0, 0, 0, 0));
TableRowGroup dataRows = null;
foreach (Block b in doc.Blocks)
{
if (b is Table)
{
Table t = b as Table;
foreach (TableRowGroup g in t.RowGroups)
{
if ((g.Tag is String) && ((String)g.Tag == "dataRows"))
{
dataRows = g;
break;
}
}
}
if (dataRows != null)
break;
}
if (dataRows != null)
{
foreach (TableRow r in dataRows.Rows)
{
double maxCellHeight = 0.0;
foreach (TableCell c in r.Cells)
{
Rect start = c.ElementStart.GetCharacterRect(LogicalDirection.Forward);
Rect end = c.ElementEnd.GetNextInsertionPosition(LogicalDirection.Backward).GetCharacterRect(LogicalDirection.Forward);
double cellHeight = end.Bottom - start.Top;
if (cellHeight > maxCellHeight)
maxCellHeight = cellHeight;
}
System.Diagnostics.Trace.WriteLine("row " + dataRows.Rows.IndexOf(r) + " = " + maxCellHeight);
}
}
Edit:
I added the FlowDocumentScrollViewer to my example. The call of "Arrange" forces the FlowDocument to calculate its heights etc. I would like to know, what the FlowDocumentScrollViewer is doing with the FlowDocument, so I can do it without the UIElement. Is it possible?

My guess would be no, you can't do it without a UIElement.
FlowDocument, by itself, doesn't actually render anything. Looking at the type in relector it looks like it is just a data type. Its about like having a string and wanting to know its size when rendered ... can't really do it without doing some kind of measure pass.
I don't know for sure, but you might get better performance in the arrange pass by passing in Double.PositiveInfinity for the size rather than 0. At least then it won't have to worry about measuring 'n' line breaks.

Related

Scroll to selection in Angular ui-grid (not ng-grid)

For my Angular JS grid work, I'm using ui-grid rather than ng-grid as ui-grid is meant to be the new version which is purer Angular.
I've got a grid that I'm populating with a http response, and I'm able to select a row (based on finding the record matching a $scope variable value) using the api.selection.selectRow method call.
What I need to do next is scroll the grid to that record.
There's an existing stack overflow question along the same lines that is for ng-grid and the answer to that refers to undocumented features which are not present in ui-grid so I can't use that approach.
The closest I've got is finding $scope.gridApi.grid to get a reference to the actual grid itself but looking through the properties and methods in the Chrome debugger doesn't show anything that sounds like it could work.
You can use the cellNav plugin. You should already have a reference to your row entity from the selection. The documentation is here.
gridApi.cellNav.scrollTo(grid, $scope, rowEntity, null);
I managed to hack together something that works pretty well but it's a bit dodgy and could probably be cleaner with a bit more Angular/jquery understanding.
I used the browser dom explorer to find that the scrollbars have a css class that we can detect to find them and then set the scroll properties on them to have the grid scroll (the grid and scrollbars are separate divs but their properties are bound so changing one updates the other).
It doesn't completely work for scrolling to the last row of the grid. This could be a timing issue, I've noticed when using breakpoints that the grid comes on screen a little larger and then shrinks down to it's final size. This could be messing with the scrolling values.
The first loop finds the height of the grid by adding up the rows, and the y position of the row for my data object (project), then we find the scrollbar and set it's scrollTop, trying to centre the row on screen without going out of bounds.
var grid = $scope.projectsGridApi.grid;
// var row = grid.rowHashMap.get(project.$$hashKey);
var found = false;
var y = 0;
var totalY = 0;
var rowHeight = 0;
for (var rowIdx in grid.rows)
{
var row = grid.rows[rowIdx];
if (row.entity.$$hashKey == project.$$hashKey)
{
found = true;
rowHeight = row.height;
}
if (!found)
{
y += row.height;
}
totalY += row.height;
}
// now find the scroll bar div and set it's scroll-top
// (todo: checking if we're at the end of the list - setting scrollTop > max means it doesn't work properly
var grid = $scope.projectsGridApi.grid;
// annoyingly this is nastily coded to find the scrollbar and isn't completely right
// I think the grid is a little taller when this is called, then shrinks
// which affects what the maximum is (so we might not always be able to put the selected item on screen if it is the last one).
var holderDiv = $('#projectsGridHolder');
if (holderDiv)
{
var scrollBarDivs = holderDiv.find('.ui-grid-native-scrollbar');
if (scrollBarDivs)
{
for (var scrollBarDivIdx in scrollBarDivs)
{
var scrollBarDiv = scrollBarDivs[scrollBarDivIdx];
var scrollBarDivClass = scrollBarDiv.className;
if (scrollBarDivClass)
{
if (scrollBarDivClass.indexOf('vertical') != -1)
{
var scrollHeight = scrollBarDiv.scrollHeight;
var clientHeight = scrollBarDiv.clientHeight;
if (rowHeight > 0)
{
y -= (clientHeight - rowHeight) / 2; // center on screen be scrolling slightly higher up
}
if (y < 0) y = 0;
else if (y > totalY - clientHeight) y = totalY - clientHeight;
scrollBarDiv.scrollTop = y;
}
}
}
}
}

Scale components in FlowLayout as big as possible

How can I scale PictureBox components to best fit the given space on the screen while keeping their aspect ratio (interdependent of the actual image or its SizeMode) ?
I tested setting the Dock of the FlowLayout and the PictureBox to Fill. I also tested using a Panel as a wrapper and tested different settings for AutoSize and AutoSizeMode.
To give more information about the background: I want to dynamically add and remove images in the viewport of the application, so a TableLayout is in the first step sort of to static. I have to admit I'm was also thinking of calculating the size an position manually - or to dynamically adapt the row and column count of the TableLayout - but it seems to me prone to errors. I thought having a FlowLayout and automatically sized components should be the correct way - but it seems not to work that way. (To speak as web developer, I simply want to "float the images left", "with width and height set to 'auto'" and no scrolling.)
The images should visualize this a bit: the first figure should point out the layout, if there is only one PictureBox - it takes the whole space (or as big as possible with the given aspect ratio). The second shows how I would like the Layout to be, if there are two (three or four) images. The third figure is showing basically a resized window with three (to six) images.
Is there some point I'm missing?
This code snippet do this:
It arranges the visible controls inside a container in respect of the aspect ratio (see R variable in the code), and uses the container margin values to get horizontal and vertical gaps between items. The padding of the container is also handled.
public static void Arrange(Control container)
{
var H = container.DisplayRectangle.Height;
var W = container.DisplayRectangle.Width;
var N = container.Controls.OfType<Control>().Count(c => c.Visible);
var R = 4 / 3d; // item aspect ratio
var margin = container.Margin;
var padding = container.Padding;
var horizontalGap = margin.Left + margin.Right;
var verticalGap = margin.Top + margin.Bottom;
if (N == 0)
return;
var bestSizedItem = (
// Try n rows
Enumerable.Range(1, N).Select(testRowCount =>
{
var testItemHeight = (H - verticalGap * (testRowCount - 1)) / testRowCount;
return new
{
testColCount = (int)Math.Ceiling((double)N / testRowCount),
testRowCount = testRowCount,
testItemHeight = (int)testItemHeight,
testItemWidth = (int)(testItemHeight * R)
};
})
// Try n columns
.Concat(
Enumerable.Range(1, N).Select(testColCount =>
{
var testItemWidth = (W - horizontalGap * (testColCount - 1)) / testColCount;
return new
{
testColCount = testColCount,
testRowCount = (int)Math.Ceiling((double)N / testColCount),
testItemHeight = (int)(testItemWidth / R),
testItemWidth = (int)testItemWidth
};
})))
// Remove when it's too big
.Where(item => item.testItemWidth * item.testColCount + horizontalGap * (item.testColCount - 1) <= W &&
item.testItemHeight * item.testRowCount + verticalGap * (item.testRowCount - 1) <= H)
// Get the biggest area
.OrderBy(item => item.testItemHeight * item.testItemWidth)
.LastOrDefault();
Debug.Assert(bestSizedItem != null);
if (bestSizedItem == null)
return;
int x = container.DisplayRectangle.X;
int y = container.DisplayRectangle.Y;
foreach (var control in container.Controls.OfType<Control>().Where(c => c.Visible))
{
control.SetBounds(x, y,
bestSizedItem.testItemWidth,
bestSizedItem.testItemHeight);
x += bestSizedItem.testItemWidth + horizontalGap;
if (x + bestSizedItem.testItemWidth - horizontalGap > W)
{
x = container.DisplayRectangle.X;
y += bestSizedItem.testItemHeight + verticalGap;
}
}
}
I put this snippet on Gist so you can contribute if you wish.

telerik listcontrol only shows part of item image

I am filling a listcontrol (Telerik for WinForms) by using the following code :
public static List<RadListDataItem> GetItems()
{
List<RadListDataItem> items = new List<RadListDataItem>();
for (int i = 1; i <= 10; i++)
{
RadListDataItem toadd = new RadListDataItem();
toadd.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText;
toadd.Text = "sssssssssss";
//toadd.Image.
string imagename = "MyProject.SuIcons.d" + i + ".JPG";
toadd.Image = new Bitmap(Assembly.GetExecutingAssembly().
GetManifestResourceStream(imagename));
items.Add(toadd);
}
return items;
}
but, only top portition of every item image is show in listcontrol, I mean I cant see the whole image associated with item in the list.
Would you help me please ?
You should set the AutoSizeItems property of the control to true in order to allow the visual items size themselves according to their content:
radListControl1.AutoSizeItems = true;
You can adjust the item size of the radListView. There is a property ItemSize that you can change in the designer view. Or if you want to do it programmatically, you can do something like this.
radListView1.ItemSize = new System.Drawing.Size(200, 400);
The first parameter is the width and the second is the height.

Listbox drag-reorder : Index of the dropped item

I'm using a Listbox wrapped inside a ListBoxDragDropTarget (from the Silverlight toolkit).
This ListBox ca be manually reordered by the user. However the last item must always be located at the bottom of the ListBox, and can't be moved at all. I found a way to cancel this last item moves, but if I drag and drop another item below this last item, it gets moved.
I can find the index of the dragged item using this code in the Drop event of the ListBox:
object data = e.Data.GetData(e.Data.GetFormats()[0]);
ItemDragEventArgs dragEventArgs = data as ItemDragEventArgs;
SelectionCollection selectionCollection = dragEventArgs.Data as SelectionCollection;
if (selectionCollection != null)
{
MyClass cw = selectionCollection[0].Item as MyClass;
int idx = selectionCollection[0].Index.Value;
}
However this only gives me the index before the drag operation.
My question is the following: Is there a way to know the new index of the dropped item ? This way, if the index = last position of the list, I can move it to the forelast position.
Thanks in advance !
Ok I found a way to do this. I bound the ItemDragCompleted event of the ListBoxDragDropTarget, and did the following:
private void dropTarget1_ItemDragCompleted(object sender, ItemDragEventArgs e)
{
var tmp = (e.DragSource as ListBox).ItemsSource.Cast<MyClass>().ToList();
SelectionCollection selectionCollection = e.Data as SelectionCollection;
if (selectionCollection != null)
{
MyClass cw = selectionCollection[0].Item as MyClass;
int idx = tmp.IndexOf(cw);
if (idx == tmp.Count - 1)
{
tmp.Remove(cw);
tmp.Insert(tmp.Count - 1, cw);
MyListBox.ItemsSource = new ObservableCollection<MyClass>(tmp);
}
}
}
As the DragSource represents the Listbox, with the new "arrangement" of items, I can therefore check if the item is now located at the end, and move it in this case.
The only problem left is that it causes a flicker on the screen, due to the item being dropped and then moved.
I'm still open to any other (best) suggestion.
I have this exact same problem. I think it may be possible to loop through the rectangles of the items in the ListBox and see if var point = args.GetPosition(myListBox); is within them. But I am hoping for an easier way...
Edit: The problem with this though, is you don't get the gravity effect that is already built in to silverlight where only half of the above and half of the below rectangle are used to drop it into the list.

How to programmatically access Control in WPF Grid by row and column index?

Once Controls have been added to a WPF Grid, is there a way to programmatically access them by row and/or column index? Something along the lines of:
var myControl = (object)MyGrid.GetChild(int row, int column);
... where GetChild is the method I wish I had!
There isn't a built-in method for this, but you can easily do it by looking in the Children collection:
myGrid.Children
.Cast<UIElement>()
.First(e => Grid.GetRow(e) == row && Grid.GetColumn(e) == column);
This answer will help you
int rowIndex = Grid.GetRow(myButton);
RowDefinition rowDef = myGrid.RowDefinitions[rowIndex];
The Children property of the grid object will give you a collection of all the children of the Grid (from the Panel class).
As far as getting the coordinates in the grid, look at the static methods in the Grid class (GetRow() & GetColumn()).
Hope that sets you off in the right direction.
System::Windows::Controls::Grid^ myGrid = nullptr;
System::Windows::Controls::UserControl^ pUserControl = nullptr;
myGrid = m_DlgOwnedObjAdmin->GrdProperties;
if (myGrid->Children->Count > 0)
{
pUserControl = (System::Windows::Controls::UserControl^)myGrid->Children->default[0];
if (pUserControl != nullptr)
{
if (bValue == true)
pUserControl->Visibility = System::Windows::Visibility::Visible;
else
pUserControl->Visibility = System::Windows::Visibility::Collapsed;
}
}
you could just give your grid column/row a name
<Grid x:Name="MainGridBackground" Grid.Column="0"/>
and access it programmatically by calling it and using "."
MainGridBackground.Background = canvasUCInstance.rectanglePreview.Fill;

Resources