I’ve got n playing map where I use the ScrollViewer to move around the map, and I wish to use the ViewBox together with PinchManipulations to zoom in and out of the map. So far I’ve done this by setting the ScrollViewer’s Manipulation mode to control, however this gives me an lag when I zoom. Is there a way to get the ViewBox and ScrollViewer to work better together and thereby avoid the lag? The code I’ve got so far is:
ScrollViewer:
<ScrollViewer Grid.Column ="0" Width="768" Height="380" HorizontalScrollBarVisibility="Hidden">
<Viewbox Stretch="None">
<View:Map/>
</Viewbox>
</ScrollViewer>
PinchZoom:
<Grid x:Name="Map" Width="1271" Height="1381.5">
<Grid.RenderTransform>
<ScaleTransform ScaleX="{Binding Path=deltaZoom}" ScaleY="{Binding Path=deltaZoom}"/>
</Grid.RenderTransform>
<i:Interaction.Triggers>
<i:EventTrigger EventName="ManipulationStarted">
<cmd:EventToCommand Command="{Binding Path=ZoomStartedCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
<i:EventTrigger EventName="ManipulationDelta">
<cmd:EventToCommand Command="{Binding Path=ZoomDeltaCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
<i:EventTrigger EventName="ManipulationCompleted">
<cmd:EventToCommand Command="{Binding Path=ZoomCompletedCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Grid>
The code where I use the pinch zoom:
public ICommand ZoomStartedCommand { get; set; }
public ICommand ZoomDeltaCommand { get; set; }
public ICommand ZoomCompletedCommand { get; set; }
private double _deltaZoom;
public double deltaZoom
{
get
{
return _deltaZoom;
}
set
{
_deltaZoom = value;
RaisePropertyChanged("deltaZoom");
}
}
public double distance;
public MainViewModel()
{
ZoomStartedCommand = new RelayCommand<ManipulationStartedEventArgs>(ZoomStart);
ZoomDeltaCommand = new RelayCommand<ManipulationDeltaEventArgs>(ZoomDelta);
ZoomCompletedCommand = new RelayCommand<ManipulationCompletedEventArgs>(ZoomCompleted);
}
public void ZoomStart(ManipulationStartedEventArgs e)
{
FrameworkElement Element = (FrameworkElement)e.OriginalSource;
var myScrollViewer = FindParentOfType<ScrollViewer>(Element) as ScrollViewer;
myScrollViewer.SetValue(ScrollViewer.ManipulationModeProperty, ManipulationMode.Control);
}
public void ZoomDelta(ManipulationDeltaEventArgs e)
{
if (e.PinchManipulation != null)
{
deltaZoom = deltaZoom * e.PinchManipulation.DeltaScale;
}
else
{
FrameworkElement Element = (FrameworkElement)e.OriginalSource;
var myScrollViewer = FindParentOfType<ScrollViewer>(Element) as ScrollViewer;
myScrollViewer.SetValue(ScrollViewer.ManipulationModeProperty, ManipulationMode.System);
}
}
public void ZoomCompleted(ManipulationCompletedEventArgs e)
{
FrameworkElement Element = (FrameworkElement)e.OriginalSource;
var myScrollViewer = FindParentOfType<ScrollViewer>(Element) as ScrollViewer;
myScrollViewer.SetValue(ScrollViewer.ManipulationModeProperty, ManipulationMode.System);
}
I had this problem too when I tried to handle a lot of data in a single container.
You can not simply load the entire map in a viewbox and use a scroll viewer and expect that everything would work fine.
The first solution to make an application to perform well on big data sets is to try UI Virtualization. A control that accepts virtualization creates only the elements needed to display the current view, or more exactly , just the elements which are visible on the display. In order to understand better this concept I will give you a real world example. Take a look at how Facebook renders its News feed. It loads X posts in the X html elements and then when the users scrolls down, it unloads non visible ones and loads the new ones.
1.UI Virtualization
It simply reuses elements. This is the concept of UI virtualization. I have to mention one important features: deferred scrolling (the user can scroll but the results are only displayed after the release of the click, this is a performance improvement if the user like to play up and down with the scrollbar)
Coming back to your problem. If the map is entirely loaded it is a performance issue to scroll or zoom into because the non visible parts would zoom and scroll also. Imagine what would had happened if Google Maps would not support UI Virtualization. When the user would try to zoom in , all Earth would zoom in(tons of GBs).
2.Data Virtualization
However your map should be fully loaded somewhere in the memory. If it is big, this is a problem and there it comes Data Virtualization.
Instead of having all map fully loaded, it loads just the needed sections for display. If you want to have a better responsiveness you could use the concept that Google Maps uses. Load few more data into memory than it fits on the display when rendered, so the UI would not freeze(until the data is brought in memory) when you try to scroll.
You can read more :
Implementing virtualized panel
Item container generator
Measure core
Results
I solved my problem using Virtualized Canvas. Sample of application using virtualized canvas: http://pavzav.blogspot.ro/2010/11/virtualized-canvas.html
You might like to take a look at Wpf Bing Maps Control
I think you should instead look at the viewportcontroller and look into the basic lens sample, where they have implemented this. The viewportcontroller, has a scrollviewer inside, and viewport.
The basics are this:
While the image you have is "unzoomed", the scrollviewer has full control and the ViewportControl does nothing.
When you start to pinch, lock the scrollviewer by disabling the verticalscrollbar AND setting the viewport.height = scrollviewer.height. This neutralizes the scollviewer.
You can do a temporary zoom using the Image ScaleTransform.
On pinch finished, resize your actual image so that it takes up real space inside the ViewportControl. Now your viewportControl will let you pan all over the zoomed image with nice bounce-back.
When you zoom back out again, re-enable the scrollviewer. (Set the height to the screen height and turn on the scrollbar.)
FYI, I completely forget why there is a canvas in there, but I feel like it is important. See below:
While the sample below does not do what you want to do, I based my code on the MediaViewer inside this sample and modified it:
Basic Lens Sample
However it should be noted that it is for picture zoom.
Related
I have a window defined in XAML that has a bing map element. Without copying the whole window, the relevant part of the XAML is
<m:Map x:Name="myMap" CredentialsProvider="BLABLA" Mode="Road">
<m:MapItemsControl ItemsSource="{Binding Devices, Mode=TwoWay}">
<m:MapItemsControl.ItemTemplate>
<DataTemplate>
<m:MapPolyline Locations="{Binding Locations, Mode=TwoWay}"
Fill="Green" Stroke="Blue"
StrokeThickness="4" Opacity="1"/>
</DataTemplate>
</m:MapItemsControl.ItemTemplate>
</m:MapItemsControl>
</m:Map>
Devices is an ObservableCollection<PushPinSet>, where PushPinSet is defined has follows
public class PushPinSet
{
public DateTime LastSeen { get; set; }
public LocationCollection Locations { get; set; }
}
When I create the ObservableCollection Devices (almost) everything is fine: the MapPolylines are drawn accordingly to the LocationCollection. I say almost, because for some unknown reason it always skips the first item. I work around it by adding a fake item at the beginning of the collection.
When I try to create a new LocationCollection in one of the items of devices in order to deform the shape, the corresponding MapPolyline is not updated.
So I have two questions:
Do you see any bug in my XAML that justifies the fact that the first item is not displayed?
Why are MapPolylines not updated? I am thinking that for some reason when I add/remove items to Devices I emit correctly the PropertyChanged event, but the same does not happen when I edit the specific item. If this is correct what and how should I signal, so that the UI works as expected?
UPDATE
By following the suggestion of mm8 I implemented INotifyPropertyChanged and it works a little bit more: the shape does not change, but i can see it change if I zoom out and in or I drag the map. Can I automatically force a redraw of the shapes? I also removed Mode=TwoWay, since it was not necessary.
I checked with a bunch of writelines, the collection's count is always n+1, where n is the number of items I want to add and the +1 is due to the fake item.
I'm trying to improve performance with my WPF application and I'm having problems with a complex ItemsControl. Although I've added Virtualization, there's still a performance problem and I think I've worked out why.
Each item contains a series of expandable areas. So the user sees a summary at the start but can drill down by expanding to see more information. Here's how it looks:
As you can see, there's some nested ItemsControls. So each of the top level items has a bunch of Hidden controls. The virtualization prevents off-screen items from loading, but not the hidden items within the items themselves. As a result, the relatively simple initial layout takes a significant time. Flicking around some of these views, 87% of time is spent parsing and Layout, and it takes a few seconds to load.
I'd much rather have it take 200ms to expand when (if!) the user decides to, rather than 2s to load the page as a whole.
Asking for advice really. I can't think of a nice way of adding the controls using MVVM however. Is there any expander, or visibility based virtualization supported in WPF or would I be creating my own implementation?
The 87% figure comes from the diagnostics:
If you simply have
- Expander
Container
some bindings
- Expander
Container
some bindings
+ Expander
+ Expander
... invisible items
Then yes, Container and all bindings are initialized at the moment when view is displayed (and ItemsControl creates ContentPresenter for visible items).
If you want to virtualize content of Expander when it's collapsed, then you can use data-templating
public ObservableCollection<Item> Items = ... // bind ItemsControl.ItemsSource to this
class Item : INotifyPropertyChanged
{
bool _isExpanded;
public bool IsExpanded // bind Expander.IsExpanded to this
{
get { return _isExpanded; }
set
{
Data = value ? new SubItem(this) : null;
OnPropertyChanged(nameof(Data));
}
}
public object Data {get; private set;} // bind item Content to this
}
public SubItem: INotifyPropertyChanged { ... }
I hope there is no need to explain how to to do data-templating of SubItem in xaml.
If you do that then initially Data == null and nothing except Expander is loaded. As soon as it's expanded (by user or programmatically) view will create visuals.
I thought I'd put the details of the solution, which is pretty much a direct implementation of Sinatr's answer.
I used a content control, with a very simple data template selector. The template selector simply checks if the content item is null, and chooses between two data templates:
public class VirtualizationNullTemplateSelector : DataTemplateSelector
{
public DataTemplate NullTemplate { get; set; }
public DataTemplate Template { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item == null)
{
return NullTemplate;
}
else
{
return Template;
}
}
}
The reason for this is that the ContentControl I used still lays out the data template even if the content is null. So I set these two templates in the xaml:
<ContentControl Content="{Binding VirtualizedViewModel}" Grid.Row="1" Grid.ColumnSpan="2" ><!--Visibility="{Binding Expanded}"-->
<ContentControl.Resources>
<DataTemplate x:Key="Template">
<StackPanel>
...complex layout that isn't often seen...
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="NullTemplate"/>
</ContentControl.Resources>
<ContentControl.ContentTemplateSelector>
<Helpers:VirtualizationNullTemplateSelector Template="{StaticResource Template}" NullTemplate="{StaticResource NullTemplate}"/>
</ContentControl.ContentTemplateSelector>
</ContentControl>
Finally, rather than using a whole new class for a sub-item, it's pretty simple to create a "VirtualizedViewModel" object in your view model that references "this":
private bool expanded;
public bool Expanded
{
get { return expanded; }
set
{
if (expanded != value)
{
expanded = value;
NotifyOfPropertyChange(() => VirtualizedViewModel);
NotifyOfPropertyChange(() => Expanded);
}
}
}
public MyViewModel VirtualizedViewModel
{
get
{
if (Expanded)
{
return this;
}
else
{
return null;
}
}
}
I've reduced the 2-3s loading time by about by about 75% and it seems much more reasonable now.
This simple solution helped me:
<Expander x:Name="exp1">
<Expander.Header>
...
</Expander.Header>
<StackPanel
Margin="10,0,0,0"
Visibility="{Binding ElementName=exp1, Path=IsExpanded, Converter={StaticResource BooleanToVisibilityConverter}}">
<Expander x:Name="exp2">
<Expander.Header>
...
</Expander.Header>
<StackPanel
Margin="10,0,0,0"
Visibility="{Binding ElementName=exp2, Path=IsExpanded, Converter={StaticResource BooleanToVisibilityConverter}}">
An easier way to achieve this is to change the default Visibility of the contents to Collapsed. In this case WPF won't create it initially, but only when a Trigger sets it to Visible:
<Trigger Property="IsExpanded" Value="true">
<Setter Property="Visibility"Value="Visible" TargetName="ExpandSite"/>
</Trigger>
Here "ExpandSite" is the ContentPresenter within the default ControlTemplate of the Expander control.
Note that this has been fixed in .NET - see the default style from the WPF sources on github.
In case you have an older version, you can still use this fixed control template to update the old one with an implicit style.
You can apply the same technique to any other panel or control.
It's easy to check if the control was already created with Snoop. Once you attached it to your application, you can filter the visual tree with the textbox on the top left. If you don't find one control in the tree, it means it was not created yet.
I have a wpf application which hosts a group of controls which are backed by another process via FrameworkElementAdapter. For some reason, these controls have a clipping / Z order issue that non-remoted controls don't seem to exhibit.
The gridview in the above image is one of those hosted controls, and improperly overlaps the panel on the right, while the contentcontrol that hosts it behaves as expected. I have explicitly set ClipToBounds = true on the gridview.
My question is :
Is there a way to make my controls clip and honor the z order properly, or does FrameworkElementAdaptermake this impossible by, for example, rendering them onto the adorner layer or something?
Xaml:
<Grid>
<Viewbox>
<ContentControl Content="{Binding VM.ErrorView}" Height="240" Width="425" Loaded="ContentControl_Loaded"/>
</Viewbox>
</Grid>
C#:
public partial class ValidationView : UserControl
{
public ValidationView()
{
InitializeComponent();
}
private void ContentControl_Loaded(object sender, RoutedEventArgs e)
{
var cc = (sender as ContentControl);
var content = cc.Content as FrameworkElement;
content.ClipToBounds = true;
}
}
Interesting side note: if I inspect my app with Snoop, in the preview Snoop displays when you mouse over parts of the visual tree graph, the remoted controls don't appear at all and their respective host contentcontrols appear empty, but are the right size
Up until today I've been using MVVM Light's EventToCommand for event handling in XAML. I decided to try out InputBinding for mouse events and so far the results have been far from pleasing. I'm guessing I'm doing something wrong because there's a delay of maybe half a second between mouse clicks. With EventToCommand, the UI will update as fast as I can click it. All this test program does at the moment is fills a circle on a canvas either white or black when it's clicked.
<Canvas>
<Ellipse Canvas.Left="{Binding X}"
Canvas.Top="{Binding Y}"
Width="16"
Height="16"
Fill="Black">
<Ellipse.InputBindings>
<MouseBinding Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type UserControl}},
Path=DataContext.ClickEllipse}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Ellipse}}}"
MouseAction="LeftClick" />
</Ellipse.InputBindings>
public RelayCommand<object> ClickEllipse { get; set; }
ClickEllipse = new RelayCommand<object>((o) => ExecuteClickEllipse(o));
private void ExecuteClickEllipse(object o)
{
var obj = o as Ellipse;
if (obj.Fill == Brushes.Black)
{
obj.Fill = Brushes.White;
TestText = "White";
}
else
{
obj.Fill = Brushes.Black;
TestText = "Black";
}
}
What am I doing anything wrong here to cause the delay between clicks? I can't imagine that this would be the intended behavior. I have EventToCommand set up almost identically and it has no problems. Furthermore, assuming I did make a stupid mistake and this isn't intended behavior, are there any advantages of InputBinding over EventToCommand for Key and Mouse events or vice versa? How do the 2 differ in terms of functionality and performance (if that's even an issue in this situation)?
Edit - Something else I've noticed with both InputBinding and EventToCommand (which I have set up almost identically) is that each time the circle is clicked, task manager shows the program's memory usage jumping up a bit. Is this normal?
I've just been looking into your problem... now I don't use that RelayCommand, instead preferring my own ActionCommand, but I've looked at the code for it and they are practically the same. So, I added your code to a new WPF applicaton and here's what I found:
When I click on the circle, it immediately changes colour. If I click on it again, it immediately changes colour again. However, if I double click on the circle, it only changes colour once and I would have expected it to change colour twice. Clicking on it multiple times makes it alternate between white and black, but only at a rate of about two per second... much slower than the rate of clicks.
Changing the Ellipse to a TextBlock and updating the Text property in the click handler had similar results. However, when I replace the MouseBinding with a simple event handler for the PreviewMouseLeftButtonDown event, this 'slow reaction time' disappears.
Continuing my testing, I then added a Button and set the same Command on its Command property (after removing it from the TextBlock). This just used my ActionCommand and it worked immediately and perfectly on every click.
So it appears that there is indeed a delay between multiple invocations of the same function when using this method of routing Commands through a MouseBinding. While this answer may not help you, I hope at least that this confirmation of the problem will help in some small way.
Regarding the memory issue, I wouldn't worry about that. WPF is often quick to ask for memory and slow to let it go.
I have a strange behaviour with VirtualizingStackPanel. I have a list with items that contains TextBlock with TextWrap="Wrap". Here is the code:
<ListBox x:Name="messagesList" ItemsSource="{Binding Messages}" >
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu>
...
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
<CheckBox Style="{Binding Own, Converter={StaticResource MsgTypeToStyle}}"
Tag="{Binding TimeString}"
IsEnabled="True">
<TextBlock Text="{Binding Content}" TextWrapping="Wrap"/>
</CheckBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
It works pretty good, but if I try to scroll very fast (using mouse on emulator, not prommatically) there is some lagging in scroll, probably HorizontallOffset sometimes calculates wrong, and in the bottom in ends with very strange result (see image, right image demostrates normal behaviour).
After research I figured out that problem in combination VirtualizingStackPanel and TextBlock.TextWrap="Wrap", if I remove one element from this couple all works correctly.
But I need virtualization because of big items count, and TextWrap for correct text displaying.
So I think about making my own implementation of Virtualizing Panel, can you please guide me, how to do this, or how to fix current problem?
UPD: The problem:
on the first two images ListBox is already(!) scrolled to the bottom (it can't be scrolled down any more), but elements placed incorrectly, correct placing shown on the right image. This happens only if you will scroll very fast.
UPD2: Thanks to Milan Aggarwal. He provided a good case of my problem here. Seems that is really a bug in ListBox. Workaround, provided doesn't fit in my scenario, because I need to interact with controls inside ListBox item.
Now I'm trying to catch ManipulationCompleted event and check if it is Inertial, if so that means scroll and I set focus to page:
void messagesList_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
if (e.IsInertial)
this.Focus();
}
P.S. thanks for good luck wishes;)
Inorder to overcome the black occurrence on scrolling you need to virtualize your scroll control. For that you should inherit IList and create a Collection of your own similar to ObservableCollection in which you will have to override the default indexer depending on your caching requirement and simultaneously maintain a cache for your items. I feel this might be what you are looking for: http://blogs.msdn.com/b/ptorr/archive/2010/08/16/virtualizing-data-in-windows-phone-7-silverlight-applications.aspx
There is a sample project on that page. Try that out.
I also feel that you are facing this problem http://blog.rsuter.com/?p=258. I guess this will be solved using virtualization itself. Hope it helps
What is the problem on 2nd screen? You mean large empty space after last message? Last message not being put at the bottom of page? Am I getting this right?
Actually i didn't try to reproduce your code and bug but my point is that since you are using 3rd party libs (Silverlight Toolkit) in your app, why not to use Coding4Fun Tools as well?
I wrote this dummy class:
public class Message
{
public string Time { get; private set; }
public string Content { get; private set; }
public Message(string time, string content)
{
Time = time;
Content = content;
}
}
Then i put this dummy code in main page constructor:
var _messages = new ObservableCollection<Message>();
for (int i = 0; i < 50; i++)
{
_messages.Add(new Message("12:40", "Teh very long string which should be wrapped. Pavel Durov gives WP7 Contest winners less money than he did for Android/iOS winners. FFFUUUUUUUUUUUUUU "));
}
this.ListBox.ItemsSource = _messages;
And in xaml i put a listbox with chat bubble control of toolkit:
<ListBox x:Name="ListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<c4fToolkit:ChatBubble>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="{Binding Content}"
TextWrapping="Wrap"/>
<TextBlock Grid.Row="1"
Text="{Binding Time}"
HorizontalAlignment="Right"/>
</Grid>
</c4fToolkit:ChatBubble>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I ran it and saw no strange behaviours, maybe little bit of lagging.
There is the result:
Hope i did some help.
Удачи с конкурсом, я б сам поучаствовал, но не верю, что за 1,5 месяца удастся сделать приличный клиент.
What is the problem at the second image? Is it that the BlackArea showed up at all, or is it that the ScrollView did not "bounceback" after the scrolling ended to fill the black area with content?
It is crucial to know this, because ... this effect is simply natural to WPF/WP7..
BTW. the scroller should "bounceback" after the over-scrolling, but it seems it has a bug and sometimes forgets to do it.
I'm asking because black/white areas are quite often not only on WP7, but and anywhere where there's a XAMLish ScrollViewer with inertial scrolling. You see, when scrolling faster than the platform can provide new items, you just scroll outside of the pre-calculated items and once you "over"scroll very far, the slowly-incoming items abruptly discover that there are no more items, hence - back empty area..
This happens mostly when some conditions happen at once: you scroll fast (yay), your bindings are slow (overly complicated expressions), your template-rendering is slow (complex non-reusable UI templates), the bindings does not know how many items there are (bound to IEnumerable not IList -> no .Count information!), or the renderer doesnt know how much height to reserve for an item (the item is not constant-height, but eveyr item has to be calculated to determine its own exact height).
If you want to correct it, you must fight with those causes first or you must implement your own Scrolling or hack into virtualizing stack panel and synchronize the feeding of items from bindingsource with the scrolling to let the scroller guess the total height better: for example, display only a small items at once, listen to scrolling events and add/remove next pack of items from head/tail of the list dynamically.. This will solve the problem by not allowing to scroll fast :)))))
I believe that constraining the item height is the easiest way. Can't you do something smart to make the items actually constant-sized?
I suggest you create the simplest possible function that computes a height for the wrapped textblock and bind to that. This way, you get the benefit of variable height items in the listbox, but get to keep using the virtualizing stack panel.