How to load large number of controls in Canvas selectively - wpf

I have a big size Canvas and a lot of small user controls placed over it. At a time only a small portion of the canvas is visible on screen. The usercontrols are created by the ItemsControl which is Data bound to a ViewModel.
<Canvas Height="10000" Width="10000" Background="White" >
<ItemsControl ItemsSource="{Binding Path=MyData}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Views:MyControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Left}"/>
<Setter Property="Canvas.Top" Value="{Binding Top}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Canvas>
Now since the number of usercontrols is huge, it takes a lot of time to load the whole canvas. I want to load only those controls which are in the visible frame currently. If I move the canvas, the usercontrols under the new visible area should be loaded. The prime reason I want to do this is because of performance lag associated with loading large number of usercontrols. I want to do it in Canvas what VirtualizingStackPanel does to StackPanel.
Is there a way to do it?
Thanks for the help

First, test loading blank user controls. That is user controls that display nothing at all. If this is fast enough it tells us that the problem is not in the canvas handling the large number of user controls but in initialising the user controls.
So, if that is the case, create a simple place holder user control that on IsVisibleChanged event firing loads the target user control using itself as the canvas.
All depends on the first test.

Look into virtualization. Instead of using Canvas, use a ListBox with your own implementation of VirtualizingPanel, that can act just as a Canvas and will only create the controls that are supposed to be visible.
There are many great resources on UI virtualization, here are two quick picks:
UI Virtualization in WPF by kirupa
Implementing a virtualized panel in WPF (Avalon) by Dan Crevier

Related

Adding shapes to Canvas. How to bind to VM

Sorry that the title is a bit vague but I could'n come up with a better one.
For arguments sake let's say I'm developing a simple drawing application, where the user just clicks and drags to draw a line (I'm not really developing that, but just to keep it simple).
I have a custom shape for the line to draw. Now I want to add new lines to the view as needed, but I'd like to use an ObservableCollection property via data binding on the view model to do that. Usaully I would use an ItemsControl. But of course the ItemsControl automatically positions it's items, which is not what I want.
Does anyone have an idea how to do that? Is there a way to disable the layout functions of an ItemsControl?
You can change the ItemsPanelTemplate of an ItemsControl so it uses a Canvas instead of a StackPanel to hold its items, then use the ItemContainerStyle to bind the Canvas.Top and Canvas.Left properties to your data object to position them.
<ItemsControl ItemsSource="{Binding MyCollection}">
<!-- ItemsPanelTemplate -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- ItemContainerStyle -->
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Top" Value="{Binding Y}" />
<Setter Property="Canvas.Left" Value="{Binding X}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
I have a blog article about the ItemsControl that explains in more detail how an ItemsControl works if you're interested.

Unable to drag drop items between bound itemscontrols

iv'e got several bound itemscontrols which all play the role of of drop target
i need to be able to drag drop items between these items controls .
the problem is that the items control's are not recognized as drop targets by the drag drop framework
The ItemsControl Panel :
<ItemsPanelTemplate x:Key="TopPipePanelTemplate">
<StackPanel></StackPanel>
</ItemsPanelTemplate>
The DataTemplate :
<DataTemplate x:Key="PipeDataItem" >
<Ellipse Width="45" Height="45" Fill="{Binding IsMine,Converter={StaticResource MyCheckerOwnerToColorConverter}}"></Ellipse>
</DataTemplate>
The ItemsControl Style :
<Style TargetType="{x:Type ItemsControl}" x:Key="ItemsControlStyle">
<Setter Property="ItemTemplate" Value="{StaticResource PipeDataItem}"></Setter>
<Setter Property="AllowDrop" Value="True"></Setter>
<EventSetter Event="Drop" Handler="ItemsControlDropTarget"></EventSetter>
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="ItemsControl_MouseLeftButtonDown"></EventSetter>
</Style>
** for this example lets take 2 of The ItemsControls :**
<ItemsControl ItemsSource="{Binding Path=Pipes[23].Checkers}" Style="{StaticResource ItemsControlStyle}"/>
<ItemsControl Grid.Column="1" ItemsSource="{Binding Path=Pipes[22].Checkers}" Style="{StaticResource ItemsControlStyle}"/>
when regularly displayed :
the empty ( Left ) one is not recognized as a drop target although it as AllowDrop set to True and
handles the Drop event ( as do all the itemscontrols in this screen , look at ItemsControl Style above )
now when i color the itemscontrol panel it s suddenly is recognized :
<ItemsPanelTemplate x:Key="TopPipePanelTemplate">
<StackPanel Background="AliceBlue"></StackPanel>
</ItemsPanelTemplate>
as if now it takes up the entire cell which it occupies ..
iv'e tried setting the panel to VerticalAlignment="Stretch" but this also had no affect
i'm trying to understand why my itemcontrols are not recognized as drop enabled ,
even though i expect that they take up that space , in addition the itemscontrol with the ellipses
is only recognized until the height of the ellipses that occupie it's content .
any ideas ?
just to clarify what i mean by Recognized as drop target is when you drag the ellipse you are able
to drop it on top of the itemscontrol .
for now iv'e just set the background as Transparent
The default background color of a panel doesn't exist, which means hit tests pass through it. To get it to register hit tests, such as mouse over events, you need to give it a background color. Usually I just use White, although you said Transparent also works and would be a better choice.
In addition, StackPanels will usually only take up the space they need. You might be better off using a Panel that stretches to fill all available space, such as a DockPanel with LastChildFill="False", and set DockPanel.Dock="Top" on your items

Scaling images in a WPF ListBox

I've started developing my code using the example from SO WPF: arranging collection items in a grid. Now, to gain cell selection capability, I renamed each ItemsControl to ListBox, because a ListBox is-a ItemsControl (XAMl somewhat simplified):
<ListBox HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" ItemsSource="{Binding YourItems}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style>
<Setter Property="Grid.Column" Value="{Binding X}"/>
<Setter Property="Grid.Row" Value="{Binding Y}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Image RenderOptions.BitmapScalingMode="LowQuality" Source="{Binding ...ImageSource, Mode=OneWay}">
</Image>
</DataTemplate>
</ListBox.ItemTemplate>
The grid is filled with glyph run test imaged, based on the code here.
Surprisingly it worked - kind of. Selection works. However, in the case of the ItemsControl, there was no scroll bar. Everything scaled nicely. When I made the window smaller, the grid cells shrunk and so did the images. When I made the window larger, everything scaled up.
Now, with the ListBox that's not the case. The images size stays fixed. If the window isn't large enough, there's an horizontal scroll bar and when the window isn't large enough, some of the images are hidden and the user needs to scroll to the right.
So, my question is: If a ListBox is-an ItemControl, why don't my images scale the same? What should I do to correct it?
This is because ListBox and ItemsControl use different styles. You might easily apply the ItemControl's default style to your ListBox:
<ListBox Style="{StaticResource ResourceKey={x:Type ItemsControl}}">

VirtualizingStackPanel doesn't clear the text of TextBoxes in the ItemTemplate

I have ItemsControl with VirtualizingStackPanel as items panel like this:
<ItemsControl Style="{StaticResource ItemsControl}" Name="itemsControl"
Margin="0,100,0,0" HorizontalAlignment="Stretch" Height="80">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Style is following:
<Style x:Key="ItemsControl" TargetType="ItemsControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ItemsControl">
<ScrollViewer VerticalScrollBarVisibility="Hidden"
HorizontalScrollBarVisibility="Visible">
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I set a collection with 100.000 elements as ItemsSource and get really good performance. Everything is fine except of one thing. When I input text in one of the text boxes and then start to scroll I see that that text appears everywhere throughout the list!
I understand what the VirtualizingStackPanel does. It's continuously loading elements that become visible as we scroll. I understand some aspects of it's virtualizing technique but I have no idea how to understand this strange behavior. I failed to find good doc's on WPF/Silverlight virtualization, so, please, explain me what is going on
VirtualizingStackPanel does not actually continiously load elements. Instead, it re-uses the existing elements (controls) and simply replaces the DataContext behind them.
So if you have an VirtualizingStackPanel with 100,000 items, and only 10 are visible at a time, it usually renders about 14 items (extra items for a scroll buffer). When you scroll, the DataContext behind those 14 controls gets changed, but the actual controls themselves will never get replaced.
If you do something like enter Text in TextBox #1, and that TextBox.Text is not bound to anything, then the Text will always show up because the control is getting re-used. If you bind the TextBox.Text to a value, then the DataContext will change when you scroll which will replace the displayed Text.
Not sure how to turn off recycling directly in a VirtualizingStackPanel but this is the syntax in a ListBox. I would have posted as a comment but I wanted formatted code.
<ListBox VirtualizingStackPanel.VirtualizationMode="Standard" />

Silverlight - Add Ellipses to a canvas dynamically with MVVM

I want to add a dynamic number of ellipses to a canvas and set the ellipse position (canvas.top, canvas.left). I tried binding to an ItemsControl but it each item (ellipse) has a container, so I cant set the ellipses position directly. I don't want an items container, I just want a canvas that contains ellipses.
Can this be done?
Try this - worked for me -- I use it to freely place textblocks on a canvas.
Re: Re: Positioning Items when Canvas is the ItemsPanel of an ItemsControl
02-26-2010 7:17 AM |
There is an alternative simpler solution that does work in silverlight 3.
<Canvas>
<ItemsControl ItemsSource={Binding MyItems}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<TextBlock Canvas.Left={Binding Left} Canvas.Top={Binding Top} Text={Binding Text} />
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
If the MyItems is a list of items that are of a class that has Left, Top and Text public properties, this works fine. I have also tested with Line and Border to draw simple bar graph graphics in silverlight 3.
From the bottom of this post:
http://forums.silverlight.net/forums/p/29753/450510.aspx#450510
Combine it with a Silverlight DataTemplateSelector and you can change the objects you draw based on view model properties:
http://www.codeproject.com/KB/silverlight/SLTemplateSelector.aspx
Ordinarily I would say use an ItemsControl in conjunction with a Canvas:
<ItemsControl ItemsSource="{Binding Ellipses}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemsContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding Left}"/>
<Setter Property="Canvas.Top" Value="{Binding Top}"/>
</Style>
</ItemsControl.ItemsContainerStyle>
</ItemsControl>
But in a display of Silverlight suckiness, the ItemContainerStyle property does not work on ItemsControl. It has support in ItemsControl, but it's not exposed by ItemsControl itself. Instead, it's up to subclasses of ItemsControl - such as ListBox - to expose it. Oh, and those subclasses have to be provided by Microsoft because the functionality is protected internal, so you can't just subclass ItemsControl and expose this stuff yourself. :S
So you could use ListBox instead, possibly by subclassing it and changing its item container to something simpler than a ListBoxItem. Or you could just use ListBox directly and fiddle around until the ListBoxItems look the way you want them to (i.e. not selected).

Resources