I have a number of items which I wish to be contained in either a ListBox or an ItemsControl. The size of the control is fixed, but the size of its contents are not. It is possible for this control to have 13 items inside, but if the user wishes, only one is displayed at a time, and takes up the entirety of the available space. Two items visible roughly splits the available space in two and so-on.
I don't "require" selection, but I do require a context menu.
If I use a ListBox, I get almost everything I want EXCEPT that if I select an item on the far right, it will be centered and the view "jumps". I don't want this. However, if I use an itemscontrol, when the size of the items contained changes ItemsControl just draws completely wrong proportions (sets width to aprox 120% of what it should be if just one item were displayed).
Which control should I use? And I do I 'fix' it to do what I want?
Related
I have a Window with a WrapPanel containing a variable number of custom items.
By clicking on an item I need to display a panel containing some information about the item selected.
While displaying this panel I'd like the WrapPanel to fill all the available space like in the picture below.
The best I could come up with was to create a UserControl with a Grid with two row, each one containing a WrapPanel.
When the information panel is collapsed I would display only the first WrapPanel which will contains all the items.
While the information panel is visible, I would show both WrapPanels, moving some of the items from the first WrapPanel to the second.
Honestly though, I don't really like it and I was wondering if there was a better solution.
I am using a WPF DataGrid that is bound to a custom ItemsSource that implements IList (and NOT IEnumerable). The custom ItemsSource performs data virtualization and only loads pages of items as needed. MyDataGrid actually inherits from DataGrid and overrides the handling of the DataGrid sort methods so that I can maintain data virtualization while sorting. I have UI Virtualization turned on for MyDataGrid.
When I run the application, MyDataGrid displays just fine, tells me I have roughly 20,000 items, and only asks my ItemsSource for the first 20 or so items. I then can click on column headers to sort various columns and again my ItemsSource only has to reload the first page of 40 items because MyDataGrid has asked to refresh only the first 20 or so.
BUT, when I click on my Last Name column to sort, MyDataGrid loses its UI Virtualization and asks my ItemsSource to load every single item even though it only needs the first 20 or so. I can watch as it asks for every single item by index (this[0]).
I've tried researching ICollectionView, UI Virtualization, and Data Virtualization and the only thing that I've read that seems it may apply is regarding multiple row selection (at DataGrid row request patterns with data virtualization I've set MyDataGrid's SelectionMode to Single so this shouldn't apply.
Another hint may be that, prior to sorting by Last Name, the first item in MyDataGrid has a last name that starts with the letter V (very late in the alphabet), and that I can actually sort the list in Descending order by last name without losing virtualization. The same thing happens with my Title column which has a first item that starts with the letter S. I don't have the problem with any of the other columns.
Any ideas?
FIGURED IT OUT!!! Looking through the call stack, it appeared that the problem was happening during the MeasureOverride of the VirtualizingStackPanel and I noticed it was calling the SyncUniformSizeFlags method. So I then went into the default style for the DataGrid and set the RowHeight to a fixed amount and it no longer causes the problem.
So, it appears that the DataGrid can lose its virtualization when a fixed RowHeight is not set. Still don't know why opting to sort in ascending order by Last Name or Title caused it to want to resync the row heights, but I can work around that one.
I recently wrote a routine that displayed a grid of images from files in a folder. I used a vertically-oriented ListBox where each row in the ListBox was a horizontally-oriented StackPanel of the images. I just had to walk the folder tree and populate the StackPanel. It was simple, quick and easy to implement.
Now I have a new requirement. I'll be receiving the images one-at-a-time from a piece of factory equipment that will be sending one every few seconds and I need to display it as I get it. I still need to display these in a grid but the images start from the lower left and proceed to the right until a row is filled up, then we start a new row directly above it. We WILL know at the start the final dimensions, but it may be more than we can fit on a screen so scrolling will have to be enabled.
What are the right controls to use for this in WPF? Is there a way to reuse my ListBox/StackPanel scheme or are there more appropriate controls for this? Remember, the images must be displayed as I get them - I can't fill up a whole StackPanel and then do a
listBoxImages.Items.Add(myStackPanel)
... or can I - can I add an empty StackPanel to a parent and then populate one image at a time afterwards? What's the right way to go about this?
Based on the fact the number of images is known at the start of the routine, I would create a view model with a collection of collections.
The first is an observable collection with elements for each row, inside the element is another collection for each column. This can be bound to an items control where the content template has a second items control with horizontal orientation.
Then, as your images arrive, you start to fill the last element first with your image references and work back up. That should create the desired effect. Not sure how that works with scrolling as you effectively want the last element to be in view first, so scrolled to the end.
Another option, using a single collection is to bind to an items control with a uniform grid panel template. This can be wired up with a DataTemplateSelector which when the data is null, displays an empty template, otherwise shows an image box. This will allow you to again add elements to your collection in any order and it mirror on the UI.
An example of the data template selector can be found here. Or search SO.
I have a ListBox that contains a number of User items that are DataTemplated to appear as UserControls in the ListBox. Each UserControl can be expanded in size. To start with, the ListBox is big enough to display them all in their unexpanded state. The problem that I have is that when a number of these UserControls are expanded together, they extend out of the ListBox's visible area. The ListBox does not recognise this fact and no ScrollBars appear, even when they are set to Visible.
I am using DoubleAnimations to alter the UserControl heights when the user clicks on a button in each one. Is there something that I have to do, or some setting on the ListBox that must be set to get it to register the size changes of the UserControls that represent its items and display ScrollBars when needed?
Edit>>>
I have tracked down the problem to a custom WrapPanel that I am using in the ListBox.ItemsPanel. When I remove it, or replace it with a standard WrapPanel, ScrollBars appear when required. I got the code for the Panel from a good article about creating custom WPF panels. Can anyone see what's missing from the code given in the article and why it might stop the ScrollBars from displaying?
I wonder whether ListBoxes normally do what you are expecting? You might try calling InvalidateMeasure/Layout on the ListBox if you know when the item sizes change, just to see?
I decided to write the custom WrapPanel code again completely and this time it worked correctly! When comparing the new version with the previous version, I could see that a + was missing from a += in a measuring calculation and so the Panel thought that the items were much smaller than they really were... hence no ScrollBars.
So, if you have this problem, check your measuring code carefully.
What is/are the main disadvantage of VirtualizingStackPanel? If it doesn't have any, then why it is not made as a default panel behavior/template in ItemsControl?
The MSDN page on the VirtualizingStackPanel Class has the following statements:
The word "virtualize" refers to a technique by which a subset of user interface (UI) elements are generated from a larger number of data items based on which items are visible on-screen.
and
Virtualization in a StackPanel only occurs when the items control contained in the panel creates its own item containers.
and
VirtualizingStackPanel is the default items host for the ListBox element.
From this it looks like for the "normal" use of a StackPanel as a host for buttons, text blocks etc. virtualisation wouldn't offer any advantages or might even impose a performance overhead. When used in a ListBox virtualisation does have benefits as a) item containers are created by the items control and b) there are likely to be more elements in the list than can be shown on the screen at any one time.