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
Related
I have a tab control where the tabs are created at runtime. The contents of the tabs will be one of several user controls, each containing hundreds of other controls. As it takes a long time to create these user controls, I'm trying to find a way to reuse them rather than creating a new instance for each tab.
I'm setting the tab page contents using a DataTemplate as follows:
<DataTemplate>
<ScrollViewer Content="{Binding Content}" />
</DataTemplate>
Where Content is the view model for the view I want to show in the tab.
Elsewhere I use data templates to map each view model to a view, e.g.
<DataTemplate DataType="{x:Type vm:MyViewModel1}">
<ctl:CacheContentControl ContentType="{x:Type ctl:MyView1}" />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:MyViewModel2}">
<ctl:CacheContentControl ContentType="{x:Type ctl:MyView2}" />
</DataTemplate>
CacheContentControl is a wrapper around ContentControl that I use for the caching:
public class CacheContentControl : ContentControl
{
private static Dictionary<Type, Control> cache = new Dictionary<Type, Control>();
public CacheContentControl()
{
Unloaded += CacheContentControl_Unloaded;
}
private void CacheContentControl_Unloaded(object sender, RoutedEventArgs e)
{
Content = null;
}
private Type _contentType;
public Type ContentType
{
get { return _contentType; }
set
{
_contentType = value;
Content = GetView(_contentType);
}
}
public Control GetView(Type type)
{
if (!cache.ContainsKey(type))
{
cache.Add(type, (Control)Activator.CreateInstance(type));
}
return cache[type];
}
}
This ensures that the DataTemplate first checks the cache to see if it can reuse a control before creating a new instance.
This works for any new tabs that are created. The first tab is slow as expected since it needs to create the initial control, but all subsequent tabs using the same control load almost immediately. The problem I'm having is that when I click back to a previous tab the control no longer appears and the tab is blank.
I guess this is happening because I can't show the same control instance on the same form multiple times which makes sense. I've been able to work around it by handing the IsVisibleChanged event for my CacheContentControl as follows:
private void CacheContentControl_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (IsVisible && ContentType != null)
{
Control ctl = GetView(_contentType);
ctl.DataContext = DataContext;
Content = ctl;
}
else
{
Content = null;
}
}
When a tab loses focus it removes the control, the tab that receives focus can then retrieve the control from the cache which seems to work.
The issue is that the speeds have gone back to being slow again and I'm not sure why. Obviously it's possible to move a control instance from one tab to another without delay as it does this every time I create a new tab. There must be something the DataTemplate does differently to change the control's parent?
I’m not sure that you need to go so far as to cache your user controls as WPF largely does this for you.
The normal approach inside a DataTemplate keyed by type is not to use a ContentControl but simply to have the UserControl as the template.
<DataTemplate x:Key=“MyViewmodelType”>
<MyViewControl />
</DataTemplate>
The TabControl will have a ContentControl which has its Content set to whichever ViewModel is applicable and WPF will automatically render it using the specified DataTemplate for that type and set the DataContext.
I have built massive WPF Views that were rendered using DataTemplates as per the simpler approach outlined above without encountering any serious performance issues and I think that you could safely start developing by simply binding ContentControl.Content to your ViewModel instance and letting WPF handle the rendering using the simpler DataTemplate approach above.
Writing a first program targeting UWP and .NET Core on Windows 10 and wanted to add drag/drop capability to include images dragged from file folders. As I want to allow the user to arrange the dropped images (well thumbnails) as they want on the target panel I chose a Canvas panel but could not get drag/drop to work. Dragging an image file over the panel just resulted in the red circle icon (no drop) being displayed. Changed the panel type to a grid and all worked fine. Any clues on how to get the Canvas panel to co-operate?
xaml
<GridView Grid.Row="1" x:Name="MainPanel" AllowDrop="True" DragEnter="MainPanel_DragEnter" Drop="MainPanel_Drop" >
</GridView>
Code behind
private void MainPanel_DragEnter(object sender, DragEventArgs e)
{
e.AcceptedOperation = DataPackageOperation.Copy;
e.DragUIOverride.Caption = "drop to add image file to collection";
e.DragUIOverride.IsCaptionVisible = true;
e.DragUIOverride.IsContentVisible = true;
}
private async void MainPanel_Drop(object sender, DragEventArgs e)
{
if (e.DataView.Contains(StandardDataFormats.StorageItems))
{
var items = await e.DataView.GetStorageItemsAsync();
// etc.
}
}
When trying to use a Canvas panel then the xaml used the keyword Canvas in place of GridView and the Grid.row="1" was omitted.
It seems some Panel control that without setting the Background or the Fill property that will make some event can not work.
Determining whether and where in UI an element is visible to mouse, touch, and stylus input is called hit testing. For touch actions and also for interaction-specific or manipulation events that are consequences of a touch action, an element must be hit-test visible in order to be the event source and fire the event that is associated with the action. Otherwise, the action passes through the element to any underlying elements or parent elements in the visual tree that could interact with that input.
For more info, see Hit testing and input events.
So we should be able to set the Background property of the Canvas that the DragEnter and Drop event can be fired.
For example:
<Canvas Background="Transparent" Grid.Row="1" x:Name="MainPanel" AllowDrop="True" DragEnter="MainPanel_DragEnter" Drop="MainPanel_Drop">
</Canvas>
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.
I cannot understand why in the hell this simple silverlight application freezes up. Here is the code:
namespace test
{
public partial class MainPage : UserControl
{
TextBlock txtword;
public MainPage()
{
InitializeComponent();
txtword = new TextBlock();
txtword.Text = "TEST";
LayoutRoot.Children.Add(txtword);
}
private void button1_Click(object sender, RoutedEventArgs e)
{
txtword.Text = "SuperDuper";
}
}
}
After the textblock is added to the layoutroot if you try to hover or click on the button you can tell that the app has frozen for some reason. Any idea what is going on here??
If i add the text block in the XAML i am able to change its text property in the button click. LayoutRoot.Children.Add() is causing the app to freeze..
From reading your comments it seems the XAML in MainPage.xaml is something like the following:
<Grid x:Name="LayoutRoot" Background="White">
<Button Content="Do stuff" Click="Button_Click" />
</Grid>
After adding the TextBlock, either in code or in XAML, you effectively end up with the following:
<Grid x:Name="LayoutRoot" Background="White">
<Button Content="Do stuff" Click="Button_Click" />
<TextBlock Text="TEST" />
</Grid>
Your Grid doesn't specify any ColumnDefinitions or RowDefinitions, so you have a 1 × 1 grid with all child controls of the Grid given the entire width and height of the grid.
As neither your Button nor your TextBlock specify a z-index value (using Canvas.ZIndex), their z-order is defined by their position within the grid's Children. The TextBlock comes after the Button, so it is the one that is 'on top'.
The TextBlock may contain only a tiny amount of text, but the TextBlock itself will still fill the Grid. TextBlocks do not automatically resize to fit the text they contain and nothing else. Your Button appears not to work because the TextBlock is on top of it and receives all of the mouse events. TextBlocks are static controls that do nothing in response to any mouse event, and this should explain why your app is appearing to freeze.
Setting the HorizontalAlignment and/or VerticalAlignment of the TextBlock to a value other than Stretch stops the TextBlock being given the entire width and height of the Grid and allows the Button to receive mouse events.
I have a SWF object embedded in a WindowsFormsHost Control inside a WPF window.
I'd like to add a toolbar over the swf movie.
The problem with the snippet of code I have below, is that when the new child is added to the host control (or the movie is loaded, I haven't figured out which yet), the toolbar is effectively invisible. It seems like the z-index of the swf is for some reason set to the top.
Here is what it looks like:
XAML:
<Grid Name="Player">
<WindowsFormsHost Name="host" Panel.ZIndex="0" />
<Grid Name="toolbar" Panel.ZIndex="1" Height="50"
VerticalAlignment="Bottom">
[play, pause, seek columns go here]
</Grid>
</Grid>
C#:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
flash = new AxShockwaveFlashObjects.AxShockwaveFlash();
host.Child = flash;
flash.LoadMovie(0, [movie]); // Movie plays, but no toolbar :(
}
Any insight on this issue would be much appreciated.
Update: Since no suitable answer was posted, I've placed my own solution below. I realize this is more of a hack than a solution so I'm open to other suggestions.
Here is my hackaround the WindowsFormsHost Z-index issue.
The idea is to place whatever you need to be overlayed nested inside a Popup. Then to update that popup's position as per this answer whenever the window is resized/moved.
Note: You'll probably also want to handle events when the window becomes activated/deactivated, so the pop disappears when the window goes out of focus (or behind another window).
XAML:
<Window [stuff]
LocationChanged="Window_LocationChanged"
SizeChanged="Window_SizeChanged" >
<Grid Name="Player">
[same code as before]
<Popup Name="toolbar_popup" IsOpen="True" PlacementTarget="{Binding ElementName=host}">
[toolbar grid goes here]
</Popup>
</Grid>
</Window>
C#:
private void resetPopup()
{
// Update position
// https://stackoverflow.com/a/2466030/865883
var offset = toolbar_popup.HorizontalOffset;
toolbar_popup.HorizontalOffset = offset + 1;
toolbar_popup.HorizontalOffset = offset;
// Resizing
toolbar_popup.Width = Player.ActualWidth;
toolbar_popup.PlacementRectangle = new Rect(0, host.ActualHeight, 0, 0);
toolbar_popup.Placement = System.Windows.Controls.Primitives.PlacementMode.Top;
}
private void Window_LocationChanged(object sender, EventArgs e)
{ resetPopup(); }
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{ resetPopup(); }
Another solution I've discovered is to use Windows Forms' ElementHost control. Since I'm using a Windows Form inside a WPF window anyway, why not just use an entire Windows Form and save myself Z-Issue headaches.
The ElementHost control is really useful, because I can still use my toolbar UserControl, and embed it inside the Windows Form. I've discovered that adding a child can be finicky with Windows Forms, so here's a snippet describing the solution:
First, toss in the ActiveX object, then an ElementHost Control, using the designer.
Form1.Designer.cs:
private AxShockwaveFlashObjects.AxShockwaveFlash flash;
private System.Windows.Forms.Integration.ElementHost elementHost1;
Form1.cs
public Form1(string source)
{
InitializeComponent();
toolbar = new UserControl1();
this.elementHost1.Child = this.toolbar;
this.flash.LoadMovie(0, source);
}
Note that the child was not set in the designer. I found that for more complex UserControls the designer will complain (though nothing happens at runtime).
This solution is, of course, still not entirely ideal, but it provides the best of both worlds: I can still code my UserControls in XAML, but now I don't have to worry about Z-indexing issues.