I got an interesting issue inside my wpf application.
I'm working with mvvm, and items are composed within a content control that bound to the view model.
For example:
<Shell>
<ScrollViewer>
<StackPanel>
<ContentControl Content="{Bidning SomeVM}"/>
<ContentControl Content="{Bidning SomeVM2}"/>
<ContentControl Content="{Bidning SomeVM3}"/>
</StackPanel>
</ScrollViewer>
</Shell>
If the stack panel got only one item it works fine, but for some reason each view (resolved form vm) require much more space(height) than it actually need.
The views contains a GroupBox and a datagrid inside if it's matter.
So this is my problem, any suggestions?
A StackPanel is going to measure it's children with Infinity in the direction of the Orientation, which is Vertical by default. So your items will be measured with an infinite height which essentially means they're going to size to content - e.g. the DataGrid will allocate, measure and arrange a record for every item in the list (assuming no constraints were set on the control). Note too that their will be no virtualization as a result since that relies on being measured with a specific constraint.
Note, even if you used a different panel you might get this behavior as the ScrollContentPresenter within the ScrollViewer will measure it's children with infinity if it is performing the scrolling - which will happen if the CanContentScroll is set to false or if the ItemsPanel doesn't implement IScrollInfo.
I'm not sure what type of layout you're looking to acheive so it's hard to offer any specific recommendations.
Related
I was making some good progress on my App, but ran into a problem that's stumped me. I was hoping someone could offer their expertise?
My app is intended to be a "scoresheet"... one of the PivotItems features a ScrollViewer (Horizontal scrolling, vertical disabled), with a Horizontal-orientation StackPanel inside. This StackPanel contains a "ScoreSheet" UserControl, which is basically a Grid with various text-boxes on it. So... kind of like this (except the ScoreSheet items are added to the StackPanel programmatically):
<controls:PivotItem>
<controls:PivotItem.Header>
<TextBlock Text="score sheet" FontSize="45" Height="80" />
</controls:PivotItem.Header>
<ScrollViewer Width="470" Height="560" VerticalAlignment="Top" x:Name="sv_scoresheets" MaxHeight="560" MinHeight="560" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled" IsHitTestVisible="False">
<StackPanel Name="sp_scoresheets" Orientation="Horizontal">
<local:ScoreSheet></local:ScoreSheet>
<local:ScoreSheet></local:ScoreSheet>
<local:ScoreSheet></local:ScoreSheet>
</StackPanel>
</ScrollViewer>
</controls:PivotItem>
The concept was that "Previous" and "Next" buttons are in the ApplicationBar at the bottom of the page, and they trigger an Animation of the ScrollViewer left or right (ie. Current Round * Width of ScoreSheet). By having "IsHitTestVisible" set to "False" on the ScrollViewer, a user can't manually move the displayed ScoreSheet to a weird position, and swiping left/right between PivotItems still works as expected.
That all worked nicely. :)
However... the problem is on the ScoreSheet control, I want a few Buttons and TextBoxes so the user can enter in a score into the grid. By having "IsHitTestVisible" on the ScrollViewer, the taps/etc. just get ignored.
I tried setting "IsHitTestVisible" to "True" and instead running a function on the ScrollViewer's ManipulationStarted event (calling "e.Complete()")... but although that lets me access controls inside the ScoreSheet, I can't swipe left/right between PivotItems anymore.
So… can anyone help? I was thinking maybe I could refine the ManipulationStarted behavior somehow, maybe "passing along" the action to the Pivot control instead of having the ScrollViewer move? Or some other way I can make the ScrollViewer "inactive" but allow the controls within to be interactive?
I'd really appreciate any help.
Thanks for your time!
Firstly, I would say that your intended usage of the scrollviewer breaks the intended design of the scrollviewer and the pivot (a common mistake windows phone developers, including myself, have made). The main reason this is generally considered a bad practice is that it implements a side ways navigation paradigm that is intended to only be provided by the Pivot control and the Panorama control.
With that said, it doesn't mean your particular use is 100% bad, I think that there are times when breaking the rules is okay. I would just encourage you to do some research and really make sure you're not going to confuse the user, or force them to learn a navigation paradigm that they are not already familiar with through the phone's operating system, and apps that properly make use of the controls provided by the SDK.
Okay, so now that's out of the way, if you determine this really is the best design for your app, you'll need to do the following to accomplish what you want:
Instead of Using a Horizontal ScrollViewer, use a Pivot control inside your Pivot control
You'll need create a header template that is essentially empty so that you don't take up any space in the upper area of inner scrolling section.
This also makes a lot more sense because the horizontal scrolling you are trying to accomplish is by "section" instead of a continuous scroll. This saves you the trouble of having to think about exactly how many pixels you are scrolling (instead you can just use XAML to change the width of your PivotItems
Disable the scrolling functionality of the pivot scroll by the user
This can be done a number of different ways of varying complexity, and almost warrants a question on it's own. See here for one way to accomplish this.
Another thing to do (which gives you more control) is to capture the ManipulationStarted, ManipulationDelta, and ManipulationCompleted events on the inner pivot item. if you detect the delta manipulation containing a horizontal component, then inside the method handler for the ManipulationDelta event, set e.Handled = true. To cancel the inner Pivot's ability to handle the horizontal scrolling. That will look something like the following:
// Event handler for ManipulationDelta event on your inner Pivot control
public void OnManipulationDelta(ManipulationDeltaEventArgs e)
{
if(e.DeltaManipulation.Translation.X != 0)
e.Handled = true;
}
As you will discover, this can provide some odd behavior. Ideally, you'll want to figure out what works best for your app, but typically you want to check for more than just != 0 on the X component, and allow the user some little bit of x, just incase they hit the control at a weird angle such that the X component has a value of 1.
Implementing the buttons to move to the right or the left:
This should be pretty simple, just make use of the Pivot.SelectedIndex property.
However, in most cases, this is a bad idea. Be REALLY sure you are a good exception to standard practice (preferably by giving your app to someone who has never used it but owns a windows phone, and see if they intuitively understand how to use your app.
Many thanks to Paul for his input and advice. A Pivot offered some extra flexibility, but still had similar issues to the ScrollViewer control when IsHitTestVisible="False" (in that buttons and other controls on the contained elements would become unusable). Also, aesthetically for this app, I found I preferred the multiple ScoreSheets being right next to each other when the Previous/Next buttons were pressed and they were animated to show the new sheet (as PivotItems, one would scroll out of view before the next scrolled into place).
After some more thought and experimentation, I achieved the desired effect like this:
<Canvas Width="470" Height="560" VerticalAlignment="Top" MaxHeight="560" MinHeight="560" MinWidth="470" MaxWidth="470">
<Canvas.Clip>
<RectangleGeometry Rect="0,0,470,560" />
</Canvas.Clip>
<StackPanel Name="sp_scoresheets" Orientation="Horizontal" CacheMode="BitmapCache">
<local:ScoreSheet /></local:ScoreSheet>
<local:ScoreSheet /></local:ScoreSheet>
<local:ScoreSheet /></local:ScoreSheet>
</StackPanel>
</Canvas>
The "Canvas.Clip" let me hide the ScoreSheets on either side of the currently visible one, and I could animate the Canvas.Left position of the StackPanel when the Previous/Next buttons were pressed.
By avoiding the ScrollViewer or Pivot, there were no events to handle/override, and the controls in the StackPanel could then be accessed as I intended.
'hopefully that might be of help to others. :)
I'm binding a list of object to an ItemsControl which contain a TextBox control.
I want to be able to change the textbox background color based on the background color property of the bind object.
It works perfectly with the INotifyPropertyChanged interface but when I need to update let say 1000 objects it takes a huge amount of time I guess because it needs to update the controls one by one.
Does someone has a tip for me to lets say, update all my objects background color and than update the binding in one shot instead of object by object?
Thanks,
Mat
If you use virtualization, it'll only need to update the items that are visible - normally items controls will only track property changes on the items that are currently visible. However, if you're using the base ItemsControl, virtualization will be off by default. To turn it on, you need to provide a custom template that includes a ScrollViewer with CanContentScroll set to True, and you also need to use a VirtualizingStackPanel, either in the control template, or via the ItemsPanel - this shows the former approach:
<ItemsControl ItemsSource="...whatever...">
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<ScrollViewer CanContentScroll="True">
<VirtualizingStackPanel IsItemsHost="True" />
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
Virtualization improves performance with large numbers of list items in various ways. It should help your scenario, but it may also improve other aspects of the performance. It's not without its problems, but I'd definitely try it here first before attempting other solutions.
I finally found why it was taking me so long to update my UI. Byt the way, What I'm trying to achieve is a kind of spreadsheet grid. My cell container was a border with the borders visible. I was updating the Border control Background color based on cell selection. Disabling the borders of the Border control resolved my issue right away. The UI update time went from 4-5 seconds to instantaneous.
Thank you guys for your answer, I will still try to implement your suggestion to improve even more my project.
Thanks
Mat
I have grid in a WPF window and a DataGrid control inside:
<Grid>
<DataGrid ItemsSource="{Binding AllAuthors}" />
</Grid>
AllAuthors is an ObservableCollection<Author> and Author a simple class with only a few string properties. The collection is populated with around 40000 objects in code behind. The DataGrid opens quite quickly (after 1 sec) and navigation through the datagrid goes smooth and fast. The application has a memory load of 35 MB.
If I replace the code above by ...
<StackPanel>
<DataGrid ItemsSource="{Binding AllAuthors}" />
</StackPanel>
... the application runs with 100% CPU load and memory grows continuously up to 1,5 GB while the application is trying to display the DataGrid. Finally I receive an OutOfMemoryException.
I'm WPF beginner and wondering now what's wrong here.
(I'm using VS2010, .NET 4.0 and the built-in DataGrid control of WPF 4.0)
Thank you for help in advance!
As long as it is in the grid, this is not a problem since probably only a few items are actually generated - the ones that are actually currently visible. This is called UI virtualization and is built into several ItemsControls in WPF. Since the DataGrid is rather small, there are not too much Items actually generated.
However when you put it in the StackPanel you might have build a layout where the StackPanel expands to the height of the DataGrid while the DataGrid takes as much space as it thinks it needs. We would need see the complete xaml to see if that is the case. Anyway, if it is, now there are actually quite a lot of items "visible" (i.e. all of them). And generating 40000 items is obviously not a good idea.
Have you compared the ActualHeight property of the two DataGrids?
I am using a TabControl to programmatically show or hide groups of form controls. I have implemented the technique described here and it approximately works as expected, except that there is a band approximately 1 or 2 pixels high in the location where the tab headers are normally displayed.
I have verified this by using Snoop to navigate the visual tree and observe the movement of the highlight rectangle as each element is selected. The size of the rectangle for the tab content element is fractionally smaller than that of the containing TabControl, which accounts for the extra pixels I am seeing. None of the elements that might affect this have margin, border or padding.
To achieve proper alignment with other controls, I need to eliminate this extra space, but I am not sure how. However, perhaps the question I should be asking is "is there a better way to selectively show / hide groups of controls?".
Thanks for your ideas,
Tim
I suppose the thin line is caused by the TabPanel which is still there even though all TabItems are collapsed.
However, you could change the TabControl's ControlTemplate and bind the TabPanel's Visibility to the number of tabs, like this:
<TabPanel ... Visibility="{Binding Items.Count, RelativeSource={RelativeSource FindAncestor, Type={x:Type TabControl}}, Converter={StaticResource ZeroToCollapsedConverter}}" ... />
Of course, you will have to implement a converter which converts 0 to Visibility.Collapsed and all other values to Visibility.Visible.
BTW: You can get the default ControlTemplate for the TabControl here.
I have a WPF User control that binds to a DataTable and generates CheckBox and a masked EditBox for each row in a DataTable. I have several instances of this control on my form. The total number of CheckBoxes to be generated is over 200. I am seeing some rendering performance issues. The form loads with all of the static controls ( Text Boxes, Drop Downs) instantly, then several seconds later the CheckBoxes appear.
Any thoughts?
Thanks
Unless all the 200 items are visible on the screen, you should be using some kind of virtual layout that creates the visual tree only for the visible items. This will greatly improve your performance.
What is "generating" the checkboxes? You should be using an ItemsControl (or subclass) and binding the data that represents the checkboxes to it. Assuming you're doing that, then what you want to do is get that ItemsControl to use "virtualizing" by applying the VirtualizingStackPanel.IsVirtualizing property to the ItemsControl like so:
<ItemsControl VirtualizingStackPanel.IsVirtualizing="true" ... >
You might also want to turn on "container recycling" which will also help performance. This is also done with an attached property:
<ItemsControl VirtualizingStackPanel.VirtualizationMode="Recycling" ... >
Too many checkboxes surely cost a lot, if you look at templates of basic controls, they are also filled with lot of uielements.
I would suggest if you can divide your UI into Tabs or Accordins that will cause less visible items on one screen as well as it will help user navigate to items easily and faster as well.
VirtualizingStackPanel will help but if your binding isnt correct it may lead to unpredicted results.
Custom Control Template:
You can also create your own custom checkbox template with least UIElements, like simple rectangle filled with different color on IsChecked property trigger. This will eliminate few animations etc that can surely imporve your rendering performance. I believe CheckBox is least important when it comes to animating UI.
When you are sure that you will be using "Text" as content, then simply create template with rectangle to show filled/empty value and put TextBlock with template binding to Content.
Try to give fixed width/height to your checkbox, whenever you fix the height/width of your controls/containers, it becomes eaiser for layout manager to render them, rather then keep on calculating and adjusting items.