I have ItemControl
It displays one panel for each record inside ObservableCollection.
My Problem is….
When size of ObservableCollection increase window can’t accommodate more panels so it displays only first six panels.
So criteria, One panel for each record inside ObservableCollection,couldn't be accomplish.
So, I need to have scroll bar so I can access each panel.
How does it can be implement?
See one screen shot below and Code of It Here
Thanks......
You need to host your panel within a ScrollViewer. This allows it to grow beyond the space available to it, whilst the ScrollViewer adds a Scrollbar.
You can do this by modiyfing the ItemsControl template:
<ItemsControl>
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer>
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
Put any controls you want to have scrollbars into the ScrollViewer. Example taken from MSDN:
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<StackPanel VerticalAlignment="Top" HorizontalAlignment="Left">
<TextBlock TextWrapping="Wrap" Margin="0,0,0,20">Scrolling is enabled when it is necessary.
Resize the window, making it larger and smaller.</TextBlock>
<Rectangle Fill="Red" Width="500" Height="500"></Rectangle>
</StackPanel>
</ScrollViewer>
Related
I have a custom UserControl looks like this:
The brown boxes are ListBoxItems in a ListBox control and there are many such items. Each item again contains a lot of other controls, like images, text blocks among others. They and a Rectangle control are fixed positioned relative to a very big canvas, wrapped in a ScrollViewer. The rectangle takes up almost the entire height of the canvas. Currently, all the boxes are rendered at once, as can be confirmed in the visual tree (in the visual tree, there are 30k+ elements with about 40 elements per ListBoxItem), because the ListBox has a height of almost the canvas' height. However, the user can only see a small portion of all the boxes (and the rectangle) at one time. The user can scroll down to bring the boxes into view and the corresponding part of the rectangle. Since all the boxes are rendered at once, the UserControl behaves very poorly in terms of performance when a view containing this UserControl is being navigated to.
Apparently, the ListBox is not virtualized in this setup. I tried to limit the height of the ListBox to the containing ScrollViewer, and then virtualizing seems to be turned on. However now the ListBox itself has an implicit ScrollViewer in it. When the user scrolls the viewport down, the boxes corresponding to the certain part of the rectangle will not be shown.
The code I use to simulate this:
Window x:Class="ListBoxVirtualizationExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Width="800"
Height="450">
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<Canvas Width="2000"
Height="2000">
<Rectangle x:Name="Rect1"
Canvas.Left="0"
Canvas.Top="0"
Width="100"
Height="100"
Fill="Red" />
<Rectangle x:Name="Rect2"
Canvas.Left="0"
Canvas.Top="600"
Width="100"
Height="100"
Fill="Green" />
<ListBox Canvas.Left="150"
Width="200"
Height="{Binding Path=ActualHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ScrollViewer}}}"
ItemsSource="{Binding YourDataSource}"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Standard">
<ListBox.ItemTemplate>
<DataTemplate>
<Canvas>
<TextBlock Canvas.Left="{Binding RectangleLeft}"
Canvas.Top="{Binding RectangleTop}"
FontSize="20"
Text="{Binding Text}" />
</Canvas>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Canvas>
</ScrollViewer>
</Window>
code-behind:
using System.Collections.ObjectModel;
using System.Windows;
namespace ListBoxVirtualizationExample
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
YourDataSource = new ObservableCollection<DataItem>();
for (var i = 0; i < 200; i++)
{
YourDataSource.Add(new DataItem {Text = $"Item {i}", RectangleLeft = 0, RectangleTop = 20 * i});
}
DataContext = this;
}
public ObservableCollection<DataItem> YourDataSource { get; set; }
}
public class DataItem
{
public string Text { get; set; }
public double RectangleLeft { get; set; }
public double RectangleTop { get; set; }
}
}
This gives me results like this when scrolled down:
But ideally, it should look something similar to this (this picture is taken when the ListBox takes the entire height of the canvas, so basically no virtualization is on) :
That is, items 25-28 should always be in the green square no matter how the user changes the viewport and scrolls up and down.
The question is: how can I improve the performance of the UserControl? Is the ListBox control the right way to do this? Are there any other ways to achieve the same effect?
The virtualization is working. Because you wrap each item into a Canvas and apply the content position absolute, you are effectively moving the TextBlock elements out of the item container: each TextBlock is rendered on the screen relative to the Canvas (the one inside the DataTemplate).
Because you assigning them a Canvas.Top of a multiple of 20 the items are stacked by force and not by the VirtualizingStackPanel that the ListBox uses as panel.
If your idea was to add a top margin of 20 to each item, you must do this from the ListBox.ItemContainerStyle:
<ListBox>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Margin" Value="0,20,0,0" />
</Style>
<ListBox.ItemContainerStyle>
<ListBox>
If your idea is to place the TextBlock elements on a Canvas to position them by the location provided by the data item (and by stacking them inside the item panel), then instead of wrapping each item's content into a Canvas you must replace the panel of the ListBox with a Canvas and set Canvas.Top and Canvas.Left on the ListBoxItem (from the ItemContainerStyle). But unless you have acustom Canvas panel that supports virtualization you lose this performance feature.
The second issue is that your ListBox is obviously the wrong size. You give it the size of the ScrollViewer which is basically the size of the viewport.
If the ListBox is supposed to stretch across the Canvas you must bind it accordingly. Bind ListBox.Height to Canvas.Height.
The following code has removed the Canvas from the DataTemplate and fixed the binding set on the ListBox.Height property to make it behave as expected.
<ScrollViewer HorizontalScrollBarVisibility="Visible"
VerticalScrollBarVisibility="Visible">
<Canvas Width="2000"
Height="2000">
<Rectangle x:Name="Rect1"
Canvas.Left="0"
Canvas.Top="0"
Width="100"
Height="100"
Fill="Red" />
<Rectangle x:Name="Rect2"
Canvas.Left="0"
Canvas.Top="600"
Width="100"
Height="100"
Fill="Green" />
<ListBox Canvas.Left="150"
Width="200"
Height="{Binding Path=Height, RelativeSource={RelativeSource AncestorType={x:Type Canvas}}}"
ItemsSource="{Binding YourDataSource}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock FontSize="20"
Text="{Binding Text}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Canvas>
</ScrollViewer>
The first thing to try is give each item in that listbox a specific size.
Your listbox isn't virtualising because it doesn't have a size for each of it's items. They are all as big as they like because each item is going in a virtualising stackpanel and has nothing sets or constrains it's size.
If you give each item a size then the virtualising stackpanel can decide how many of them should fit and which will not.
That will of course virtualise those that do not fit.
You could do that with the canvas.
The height here is just arbritrary. You should pick whatever suits size of your rectangles. Maybe that's 50 * 1900. It's the height that matters, obviously.
<ListBox.ItemTemplate>
<DataTemplate>
** <Canvas Height="22" Width="200"> **
<TextBlock Canvas.Left="{Binding RectangleLeft}"
Canvas.Top="{Binding RectangleTop}"
FontSize="20"
Text="{Binding Text}" />
</Canvas>
</DataTemplate>
</ListBox.ItemTemplate>
I don't really follow why you have a canvas in each of the items though. It doesn't make much sense without an explanation. But maybe that doesn't matter so much.
If you can get the ui items down to around 10,000 then I think you'll likely find your problems greatly reduced.
If not then you could consider simplifying the controls in each item somehow. Difficult to say exactly what would be best without knowing more about the intent of this design. A drawingvisual encapsulating everything in an item into one thing might be something to consider.
I don't really follow why the whole thing is not one listbox with a canvas as itemspaneltemplate and everything rendered from that. You would just have the one scrollviewer to deal with.
With this scenario the 2000 x 2000 canvas would be the panel in the listbox and everything would be rendered in that.
It would virtualise ( if you tell it how big things are ).
I want to add scroll view to my program and I tried ScrollView control but that don't take effect. It's my first time dealing with scrolls please help me :).
My xaml Code:
<DockPanel Grid.Row="1" Background="#FF695887">
<ScrollViewer VerticalScrollBarVisibility="Auto" Height="795">
<Canvas Name="zemelapis" Height="Auto" Width="Auto">
<Image Name="pav_kelias" />
<Image Name="car1" />
</Canvas>
</ScrollViewer>
</DockPanel>
Because these 2 images is not fitting here I need a scroll for them. Maybe I should use ScrollBar?
My program example: https://gyazo.com/a4ba7e4d5004632e2229a87e686c4c09
, as you can see bottom image is not fitting in range of window.
You have specified Auto as Height and Width. This implies that the Canvas will fill the height available to it.
From the documentation:
Auto sizing behavior implies that the element will fill the height
available to it.
In this case the available height is the height of the ScrollViewer.
If you want the Canvas to be bigger, and hence the ScrollViewer to scroll, you should set a height on Canvas that is bigger than the height of ScrollViewer.
So, for example:
<DockPanel Grid.Row="1" Background="#FF695887">
<ScrollViewer VerticalScrollBarVisibility="Auto" Height="795">
<Canvas Name="zemelapis" Height="1000" Width="Auto">
<Image Name="pav_kelias" />
<Image Name="car1" />
</Canvas>
</ScrollViewer>
</DockPanel>
If you want your ScrollViewer to work easily, use a Grid instead of a Canvas:
<DockPanel Background="#FF695887">
<ScrollViewer >
<Grid Name="zemelapis">
<Image Name="pav_kelias" Source="acteurs.png"/>
<Image Name="car1" Source="public.jpg"/>
</Grid>
</ScrollViewer>
</DockPanel>
As explain by Domysee, Canvas gives you total control of the layout. Grid however will automatically adjust its size depending on the content.
See http://www.wpf-tutorial.com/panels/introduction-to-wpf-panels/
I was trying to implement a ScrollViewer like so;
<Height="auto" Width="auto"
MaxHeight="500" MaxWidth="400"
ResizeMode="NoResize" WindowStyle="None">
<Grid>
<StackPanel>
<ScrollViewer Name="scrlBr">
<StackPanel Orientation="Vertical">
<TextBlock Name ="txtBlock" Margin="10" Height="auto"
Width="auto" TextWrapping="Wrap"></TextBlock>
<Button Name="btnOk" Click="btnOk_Click" Width="80"
HorizontalAlignment="Center">Close!</Button>
</StackPanel>
</ScrollViewer>
<Label HorizontalAlignment="Center" FontSize="3"
Name="lblScrollDown">\/</Label>
</StackPanel>
</Grid>
</Window>
The problem I'm having is that the scroll bar appears disabled, while the text obviously goes down off the window and I can't see the btnOk. Surely if the window has a fixed height and the TextBlock and Button, which are contained in the Scrollviewer, are bigger than the window then the ScrollBar should be enabled, no?
UPDATE
I worked out that the problem lies in having the ScrollViewer within a StackPanel. Might try it with a Grid instead... Update to come.
SOLUTION
I was right about the Stackpanel being the problem and went with Heinzi's suggestion of using the DockPanel and all's workin' fine. :) Thanks!
The problem is your StackPanel. It always takes all the vertical space it needs, so the container of the ScrollPanel is much larger than the window itself, and the ScrollViewer sees no need to scroll.
Solution: Replace the StackPanel with a DockPanel. Move the <Label> declaration to the top and dock it to the bottom of the DockPanel. The last child in a DockPanel (which is the ScrollViewer in this case) always fills the remaining space:
<Grid>
<DockPanel>
<Label DockPanel.Dock="Bottom" ... Name="lblScrollDown">\/</Label>
<ScrollViewer Name="scrlBr">
...
</ScrollViewer>
</DockPanel>
</Grid>
Try setting the CanContentScroll property on True from the ScrollViewer and set a fixed height of the StackPanel, as a StackPanel is designed to grow indefinitely in one direction. Or better, use a different panel to stack your items in.
Maybe you need write something like this
<ScrollViewer Name="scrlBr" VerticalScrollBarVisibility="auto" HorizontalScrollBarVisibility="auto">
...
</ScrollViewer>
I am working on a WPF application and using a scrollViewer to view the content that is offlimit the screen area. Working fine, no issues.
But if my window contains a Listbox or Grid or anything like that and that control has many records then instead of adding a scrollbar to itself, it just increase the height of the control and window itself because scrollviewer believes it needs to extend.
I don't want to hardcode the listbox's height, because that make it same in different resolutions, i want to make it increase its height but not always as scrollviewer make it do so.
Thanks
You can't include a variable height/width object inside a ScrollViewer without setting the height/width of the viewer.
The scroll viewer effectively has infinite height so the grid expands to fill the "available" space - hence the effect you are seeing. As #JoeWhite says in his comments, the ScrollViewer is a container that can be as tall as it needs to be to hold all of its content. Anchoring wouldn't help - in effect, your ListBox already is anchored, it's just anchored to something that says "oh, I'll be whatever size you need me to be".
You'll need to either restrict the height, move the ListBox outside the ScrollViewer or use something other than a ScrollViewer.
Again quoting #Joe "These are the hazards of putting a scrolling area inside another scrolling area - it's poor usability and it's hard to define the behaviour."
You can wrap ScrollViewer into Grid, and bind scrollviewer's Width and Height properties to grid's ActualWidth and ActualHeight. So the scrollviewer will have fixed size equal to the size of the grid which will change when window resize.
Example:
<Grid x:Name="LayoutRoot" Background="White">
<Grid Background="#FFF1F1F1" Height="49" VerticalAlignment="Top">
<Button Content="Обзор" Margin="0,13,175.25,0" VerticalAlignment="Top" FontSize="14.667" HorizontalAlignment="Right" Width="95.147">
</Button>
<Label Content="{Binding DocPath, Converter={StaticResource FileNameConverter}, FallbackValue=Выберите файл, TargetNullValue=Выберите файл}" HorizontalAlignment="Left" VerticalAlignment="Top" Width="342.603" Margin="10,10,0,0" Height="33"/>
<Button Content="Загрузить данные" HorizontalAlignment="Right" Margin="0,13,10,0" VerticalAlignment="Top" Width="151.147" FontSize="14.667">
</Button>
</Grid>
<Grid x:Name="scrollBorder" Margin="10,54,10,10">
<ScrollViewer x:Name="LogScroller" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
HorizontalAlignment="Left" VerticalAlignment="Top"
Height="{Binding ActualHeight, ElementName=scrollBorder}" Width="{Binding ActualWidth, ElementName=scrollBorder}" >
<ItemsControl ItemsSource="{Binding Log}" />
</ScrollViewer>
</Grid>
</Grid>
I am having pretty big problem with windows forms controls hosted in WPF. When, for example, user scrolls the window, the hosted control goes on top of the window, although it should be hidden.
I know this is known problem, and default behavior of hosted controls, but I think it can be solved if control's visibility is somehow binded with: whether other controls overlap it, or not. If other controls are overlapping, it should become Collapsed or Hidden, if not, it should be Visible.
I made some kind of solution for this, but I did it on ScrollChanged event of a ScrollViewer and it works only in special situations. If somebody knows how to achieve that with binding, so it can be applied to any hosted control, please share your ideas.
For this same problem, we implemented something curious...
Windows forms host is unaffected by Z-order so scroll viewer wont be able to partially hide/ clip it for the area which is visible under the scrollviewer.
So we had two options...
Use Windows form host to host rest of the WPF UI in it which means we reverse the ownership of the UI. The WindowsFormsHost must host all the UI in it having a WinForms based scroll viewer which in turn will host the WPF UI.
Implement a scroll offset for calculated height of the windows forms host and when user scrolls add this offset to the scrollviewer's position and hide the windforms host yourself (Visibility = Hidden and NOT Collapsed). This way it gives an effect that you cannot partially scroll a winforms host but that scroll it completely off the scroll viewer. And because winformshost is Hidden (not collapsed) it continues to occupy that much height inside the invisible area under the scroll viewer (thereby maintaining its scroll position).
Let me know if this guides you in correct direction.
You can do a little trick. When you declare an WindowsFormsHost, it's parent is first HWND component. Usually it's root window. So, clip area for controls is whole window.
I'll show an example with WPF ScrollViewer.
<Window>
<Grid>
<ScrollViewer Margin="20,50">
<ItemsControl ItemsSource="{StaticResource StringArray}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<WindowsFormsHost>
<wf:Button />
</WindowsFormsHost>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</Window>
In this case behaviour will be like you described. Buttons will be out of ScrollViewer bounds.
But there's a way to create "intermediate" HWND item to clip WinForms area over ScrollViewer. Just place another WindowsFormsHost with ElementHost like below:
<Grid>
<WindowsFormsHost Margin="20,50">
<ElementHost x:Name="This is clip container">
<ScrollViewer>
<ItemsControl ItemsSource="{StaticResource StringArray}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<WindowsFormsHost>
<wf:Button />
</WindowsFormsHost>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</ElementHost>
</WindowsFormsHost>
</Grid>
Now clip area for buttons is ElementHost and WinForms Buttons will be clipped by it on scrolling.
Also you can create ControlTemplate for ContentContol and reuse it where you need it.
<ControlTemplate x:Key="ClipConteiner" TargetType="{x:Type ContentControl}">
<WindowsFormsHost>
<ElementHost>
<ContentPresenter />
</ElementHost>
</WindowsFormsHost>
</ControlTemplate>
<Grid>
<ContentControl Template="{StaticResource ClipConteiner}" Margin="20,50">
<ScrollViewer>
<ItemsControl ItemsSource="{StaticResource StringArray}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<WindowsFormsHost>
<wf:Button />
</WindowsFormsHost>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</ContentControl>
</Grid>