Complex UI Inside Listboxitem

Complex UI inside ListBoxItem

To answer the overarching question - how to do this in WinForms - I'd suggest the following:

  1. Use a WPF ListBox in your WinForms application, wrapped in an ElementHost. This has its own issues, but I think it's the cleanest way to get the desired effect.

    if that doesn't fit the bill, then

  2. Use a third party control suite that has components which support this (Infragistics and DevExpress both do).

  3. Spin your own derived ListBox control that overrides paint, etc to render the desired content.

To your individual questions:

  1. Is there any way to achieve the same in Windows Forms, all while maintaining separation of concerns between the View and the Application Logic in such a way that if I later wanted to completely redefine the View, I wouldn't have to refactor the entire application?

    In the past, I've used the MVP (model-view-presenter) paradigm with Windows Forms. It works for separating the view from the business logic, albeit not as cleanly as an MVVM approach with WPF. The best advice I can give is: don't put business logic in event handlers.

  2. Does winforms support databinding in such a way that each of my ListBoxItems can be bound to a complex Entity, eventually including an intermediate type conversion from Model data to UI data and back, in such a way that I don't have to write tons of boilerplate code to populate the view and then pass the UI values back into the Model in order to save?

    No. Windows Forms databinding does not support complex data binding. You could implement something yourself via ICustomTypeDescriptor or IBindingSource that can take complex paths and evaluate them for binding purposes...but nothing exists out of the box for this.

  3. What if I wanted to introduce Animations in such a way that the currently SelectedItem would animatedly expand itself into some kind of "Row Details" mode, where you can see a lot of additional information?

    You'd have to roll your own Windows Forms ListBox and ListBoxItems and override the paint operations.

  4. Does winforms support UI Virtualization in such a way that if I have, say 1 million items it doesn't take a lifetime to load the UI, and only render what's visible on screen?

    Not out of the box, but some third party control suites have components that support types of virtualization...but not at all in the same way WPF does.

  5. Say I wanted to introduce complex graphics to the equation. Is winforms rendering hardware-accelerated?

    Windows Forms is based on GDI+. GDI+ is not hardware accelerated: Windows Forms very slow under Windows7?

  6. How do I make all this Resolution Independent in such a way that the ListBox and all its contents stretch to the available window size in order to leverage larger screens while maintaining compatibility with smaller ones?

    You can use Docking and Anchoring in Windows Forms to accomplish this. Or you can add custom event handlers to perform appropriate layout adjustments based on resolution and Window size.

  7. It's been suggested to use the ListView control instead of a regular ListBox, does the ListView provide the ability to add ANY UI into it? can I add Videos for example for each item? or a complex Master/Detail template with Save and edit Buttons?

    This is simplifying...but a ListView is simply a ListBox that supports multiple view types. It is also more limited in terms of databinding. http://blog.gfader.com/2008/09/winforms-listbox-vs-listview.html.

  8. Does winforms provide a consistent and adequate [Document Model][2] that enables the creation of high-fidelity WYSIWYG documents and other types of rich content?

    No. Not at all. Not even a little bit.

In short, if it's an acceptable solution, I'd wrap your WPF ListView in an ElementHost and call it a day.

Get Listboxitem from listbox

WPF's main thread runs items at different priority levels. Code that runs in the Constructor all gets run at Normal priority, while things like rendering the ListBox and it's items run at the Render priority level, which occurs after all Normal priority operations have finished.

This means that your entire Constructor gets run (including SetListBoxDataTemplate()) before your ListBox is even rendered and the items get generated.

If you want to run some code after the items are generated, use the ItemsContainerGenerator.StatusChanged event

// Constructor
MyListBox.ItemContainerGenerator.StatusChanged += MyListBox_GeneratorStatusChanged;

...

void MyListBox_GeneratorStatusChanged(object sender, EventArgs e)
{
// return if containers have not been generated yet
if (MyListBox.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
return;

// remove event
MyListBox.ItemContainerGenerator.StatusChanged -= MyListBox_GeneratorStatusChanged;

// your items are now generated
SetListBoxDataTemplate(MyListBox);
}

What are you trying to accomplish with this method anyways? It is a bit unusual for WPF, and there may be a much better WPF way of accomplishing your task.

Updated based on new code added to Question

A much better method of setting your Text and ItemsSource properties is to make use of WPF's data bindings.

Your DataTemplate should look like this:

<DataTemplate x:Key="ListBoxItemDataTemplate1">
<StackPanel Orientation="Horizontal">
<Border BorderBrush="Black" BorderThickness="1 1 0 1" MinWidth="50">
<TextBlock Text="{Binding Text}" Background="{Binding ElementName=ListBoxItemComboBox1, Path=SelectedValue}" >
</TextBlock>
</Border>
<ComboBox ItemsSource="{Binding ListColors}" />
</StackPanel>
</DataTemplate>*

A DataTemplate is like a cookie cutter. It's used to make the UI objects, but is not part of the UI object itself. All it does is tell WPF that "When you go to render this object, render it using this XAML". So the way your XAML gets rendered is

<ListBoxItem>
<StackPanel>
<Border>
<TextBlock Text="{Binding Text}" />
</Border>
<ComboBox ItemsSource="{Binding ListColors}">
</StackPanel>
</ListBoxItem>

In addition, the DataContext behind your ListBoxItem is the item from the collection bound to ListBox.ItemsSource, which based on your code should be CustomDataTemplateObject. That allows the bindings from the DataTemplate to work

If you're new to WPF and struggling to understand how exact the DataContext works, I'd recommend reading this article of mine: What is this "DataContext" you speak of?.

To summarize, WPF has two layers to an application: the UI layer and the Data Layer (DataContext). When you perform a basic binding like above, you are pulling data from the data layer into the UI layer.

So your ListBoxItem has a data layer of CustomDataTemplateObject, and the TextBlock.Text and ComboBox.ItemsSource bindings are pulling data from the data layer for use in the UI layer.

I'd also highly recommend using a utility like Snoop which lets you view the entire Visual Tree of a running WPF application to see how items get rendered. Its very useful for debugging or learning more about how WPF works.

WPF ListBox with a ListBox - UI Virtualization and Scrolling

The answer here is surprising:

  • If you use ItemsControl or ListBox you will get the behavior you are experiencing, where the control scrolls "by item" so you jump over a whole document at once, BUT
  • If you use TreeView instead, the control will scroll smoothly so you can scroll through your document and into the next one, but it will still be able to virtualize.

I think the reason the WPF team chose this behavior is that TreeViewcommonly has items that are larger than the visible area, whereas typically ListBoxes don't.

In any case, it is trivial in WPF to make a TreeView look and act like a ListBox or ItemsControl by simply modifying the ItemContainerStyle. This is very straightforward. You can roll your own or just copy over the appropriate template from the system theme file.

So you will have something like this:

<TreeView ItemsSource="{Binding documents}">
<TreeView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</TreeView.ItemsPanel>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<ContentPresenter /> <!-- put your desired container style here with a ContentPresenter inside -->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<DataTemplate TargetType="{x:Type my:Document}">
<Border BorderThickness="2"> <!-- your document frame will be more complicated than this -->
<ItemsControl ItemsSource="{Binding pages}">
...
</ItemsControl>
</Border>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>

Getting pixel-based scrolling and ListBox-style multiselect to work together

If you use this technique to get pixel-based scrolling, your outer ItemsControl which shows the documents cannot be a ListBox (because ListBox is not a subclass of TreeView or TreeViewItem). Thus you lose all of ListBox's multiselect support. As far as I can tell, there is no way to use these two features together without including some of your own code for one feature or the other.

If you need both sets of functionality in the same control, you have basically several options:

  1. Implement multi-selection yourself in a subclass of TreeViewItem. Use TreeViewItem instead of TreeView for the outer control, since it allows multiple children to be selected. In the template inside ItemsContainerStyle: Add a CheckBox around the ContentPresenter, template bind the CheckBox to IsSelected, and style the CheckBox with control template to get the look you want. Then add your own mouse event handlers to handle Ctrl-Click and Shift-Click for multiselect.

  2. Implement pixel-scrolled virtualization yourself in a subclass of VirtualizingPanel. This is relatively simple, since most of VirtualizingStackPanel's complexity is related to non-pixel scrolling and container recycling. Dan Crevier's Blog has some useful infromation for understanding VirtualizingPanel.

SelectedItem issue in WPF ListBoxItem MVVM

The code you provided is working as expected. What you describe in the comments, is that during debugging you see {ClientRatesWPF.Model.PositionAttributes} as a value being set to SelectedAttributeMaster.

This is completely correct, because the debugger doesn't know how to show you anything more meaningfull. You can do two things:

1) Apply the DebuggerDisplay attribute to your class:

[DebuggerDisplay("Description = {PositionAttributeMasterDescription}")]
public class PositionAttributes
{
public string PositionAttributeMasterDescription { get; set; }
}

2) Use the Visual Studio debugger to show you additional information:
Screenshot Visual Studio while break on property setter



Related Topics



Leave a reply



Submit