WPF - Exclude element from scrollbar effect in one way - c#

Is there any way to exclude certain UIElement from a certain scroll effect? I have a grid element below a columns header element. while I'd like to have full scrolling over the grid, I want the header to be affected only by the horizontal scrolling. so that it will always stay at the top when scrolling down.
Here is a mspaint I made to explain in case my explenation didn't make sense.

Why not move your scroll viewer inside the green rows and then have a different scroll viewer for the header.
Then wrap it all in a parent container
So like
<Grid>
<HeaderControl>
<ScrollViewer />
</HeaderControl>
<BodyControl>
<ScrollViewer />
</BodyContent>
</Grid>
Wrapping them together in one scroll viewer is overly complicated. Splitting it into two distinct scroll viewers will simplify the problem IMO.

What if you used 2 separate scroll viewers - an outer one that contains both the header and the grid, and that only handles the horizontal scrolling.
Then the grid itself can be wrapped in a separate scroll viewer that handles just the vertical scrolling on of the grid. E.g:
<ScrollViewer VerticalScrollBarVisibility="Disabled">
<!-- Header -->
<Grid>
</Grid>
<ScrollViewer HorizontalScrollBarVisibility="Hidden">
<!-- Content -->
<Grid>
</Grid>
<ScrollViewer />
</Grid>
Although I'm not 100% sure about the two visibility settings - you might have to play around with them to see what works

I just wanted to do the same. My solution would be using a Canvas for the header and Scrollviewer for the body.
<Canvas>
<Grid Canvas.Left="{Binding ElementName=ValueScrollViewer, Path=HorizontalOffset, Converter={StaticResource DoubleMultiplyingConverter}, ConverterParameter=-1}">
Header
</Grid>
</Canvas>
<ScrollViewer Name="ValueScrollViewer" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" >
<Grid>
Table
</Grid>
</ScrollViewer>
With converter:
public class DoubleMultiplyingConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var factor = System.Convert.ToDouble(parameter, CultureInfo.InvariantCulture);
var val = System.Convert.ToDouble(value);
return val * factor;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

To handle all the situations, you should separate static part (header) and scrollable part (body) and wrap body with ScrollViewer. This solution is perfect in everything, except the situation when your control is wider than screen (or outer control), so horizontal scroll should be allowed too and header should be also scrolled. You can't just use inner scrollviwer for this, because vertical scrollbar will only be visible when horizontal scroll is at right edge.
You can't exclude header from only vertical scroll, but you can simulate horizontal one. You can do it with just translation transform.
<Grid>
<!-- body part. place it first to make it go to the background -->
<ScrollViewer HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible" x:Name="scrollbar">
<!-- the body control itself -->
<Canvas x:Name="PART_Body"/>
</ScrollViewer>
<!-- header part. Border and margin are used to make it not overlap vertical scrollbar -->
<Border Margin="0,0,18,18" ClipToBounds="True">
<Canvas x:Name="PART_Header">
<Canvas.RenderTransform>
<!-- bind X to the scrollbar X position -->
<TranslateTransform X="{Binding ElementName=scrollbar, Path=ContentHorizontalOffset, Converter={StaticResource InvertDoubleConverter}}"/>
<!-- InvertDoubleConverter is just a value converter which multiples incmoing double value by -1 -->
</Canvas.RenderTransform>
</Canvas>
</Border>
</Grid>

Related

Is it possible to control Composition XAML ElementVisual Clipping?

I am using UWP and working with the Composition API to programmatically scale child text visuals that are nested in a typical XAML hierarchy. The textblocks in our app are contained in things like borders and a number of those borders are items contained in a GridView.
In many of the scenarios I am experiencing clipping of the associated text visual as it scales to be larger than some of the XAML containers that host the elements and I would like the visual to not get clipped as it scales to be larger than its parent.
Here is a barebone example that demonstrates some of the problems I am seeing…
My test app starts as a blank UWP app and the root grid of my page contains the following Gridview:
<GridView >
<GridViewItem>
<Border PointerPressed="Border_PointerPressed" CornerRadius="5" Width="125" Height="125">
<Grid>
<TextBlock Text="Content String 1" />
</Grid>
</Border>
</GridViewItem>
<GridViewItem>
<Border PointerPressed="Border_PointerPressed" Width="125" Height="125">
<Grid>
<TextBlock Text="Content String 2" />
</Grid>
</Border>
</GridViewItem>
<GridViewItem>
<Border PointerPressed="Border_PointerPressed" Width="125" Height="125">
<Grid>
<TextBlock Text="Content String 3"/>
</Grid>
</Border>
</GridViewItem>
</GridView>
The codebehind file contains the following additional using statements, a variable declaration, variable initialization in page constructor and this event handler:
using System.Numerics;
using Windows.UI.Composition;
using Windows.UI.Xaml.Hosting;
Compositor compositor;
public MainPage()
{
this.InitializeComponent();
compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
}
private void Border_PointerPressed(object sender, PointerRoutedEventArgs e)
{
var content = VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(sender as FrameworkElement, 0), 0);
var visual = ElementCompositionPreview.GetElementVisual(content as FrameworkElement);
var animation = compositor.CreateVector3KeyFrameAnimation();
animation.InsertKeyFrame(0f, new Vector3(1.0f, 1.0f, 0.0f));
animation.InsertKeyFrame(0.5f, new Vector3(3.0f, 3.0f, 0.0f));
animation.InsertKeyFrame(1f, new Vector3(1.0f, 1.0f, 0.0f));
animation.Duration = TimeSpan.FromMilliseconds(5000);
visual.StartAnimation(nameof(visual.Scale), animation);
}
When you run the app and click on each of the strings you should initially notice that the first string behaves differently than the other two string.
The first string gets cropped at the Border's bounding box whereas the other two strings do not.
Also note that the other two strings appear to scale past the bounds of last item and out into the page, but that turns out to probably be due to the gridview autosizing to fill the page.
The difference between the first string and the other two is that the border has a corner radius property set on it. We use cornerradius setting in our application, so it would be nice to know if there is a way to override or control this behavior so that it doesn't clip the visual as it scales.
The other behavior that is causing us problems is that at the GridView bounds is another boundary that the visual is clipping at as it scales. If you set any property (like HorizontalAlignment="Center") on the Gridview that causes it to size itself to only be as big as it needs to be, then the visual gets cropped at the controls boundaries.
Is there anything within the Compositional API that allows me to prevent or influence this clipping behavior?

Pins on a map like UI

Sorry if the title isn't very clear, but I didn't know how to describe exactly what I'm looking for.
So what I've got is a very large image in a scrollviewer:
<ScrollViewer x:Name="scrollView" ZoomMode="Enabled" HorizontalScrollMode="Enabled" VerticalScrollMode="Enabled" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible" MinZoomFactor="1" ViewChanged="scrollView_ViewChanged">
<Grid Width="{Binding Path=ViewportWidth, ElementName=scrollView}">
<Image x:Name="img" Source="The Known World.jpg" Stretch="UniformToFill"/>
</Grid>
</ScrollViewer>
And I want to place pin like UI elements that will stick to a location on a map, but will not scale along with the scrollviewer. Like the name of towns on Google Maps. I've tried various things, such as having:
<Scrollviewer>
<Grid Width="{Binding Path=ScrollableWidth, ElementName=scrollView}"
Height="{Binding Path=ScrollableHeight, ElementName=scrollView}">
<!--Pins here-->
</Grid>
<Scrollviewer>
But nothing that behaves as expected. What can I do to get this behavior?
It's a really interesting and tricky question at the same time.
I would approach it with a Canvas which I can use to properly place my pins on top of the image and ensure that their locations will remain fixed regardless of the scroll viewer position.
The tricky part comes when you say you want the pins to not scale with the scroll viewer. The only idea I have there is you can try to track the zoom scale on the scroll viewer, and apply an inverse of that to the pins (so if at scroll Viewer ZoomScale of 1, you have a scale of 1 on your pins, at ZoomScale = 2, you have PinScale = 1/2)
Not sure that would work, but it's worth a try.
Here's a solution I found, inspired by Alex Drenea's answer:
<ScrollViewer x:Name="scrollView" ZoomMode="Enabled" HorizontalScrollMode="Enabled" VerticalScrollMode="Enabled" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible" MinZoomFactor="1" ViewChanged="scrollView_ViewChanged" ViewChanging="scrollView_ViewChanging" Visibility="Visible">
<Viewbox x:Name="vb" Height="{Binding Path=ViewportHeight, ElementName=scrollView}">
<Grid>
<Image x:Name="img" Stretch="UniformToFill" Source="The Known World.jpg"/>
<local:PinPoint Text="Hardhome" MinZoom="0.9" Size="{Binding ElementName=scrollView, Path=ExtentHeight, Converter={StaticResource conv},ConverterParameter=scrollView}" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="2825,261,0,0"/>
</Grid>
</Viewbox>
</ScrollViewer>
MinZoom is a property on my custom UserControl that controls the ScaleTransform.
And this is the converter:
public class ViewBoxConstantFontSizeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return 100 / (double)value * 20;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
Interesting :)
My idea is ... put a pin layer over the scroll viewer.
<Grid>
<ScrollViewer x:name="imageSv" >
</ScrollViewer>
<Canvas x:name="pinCanvas" />
</Grid>
You may know the "image-based" xy position of the pin. And, when the user scroll or zoom in/out the scroll viewer, you can get the xy offset and zoom ratio. Then, you can calculate the overlayed xy position of the pin, and draw it on pinCanvas.
Advantage of this idea is - you can avoid the pin scaling process.

Changing size of the frame and elements in UWP

I want to know if there is a way to resize the frame and the elements inside of it when the window size change. I always want to have the same proportion in the size of the elements inside the frame.
Example:
1- Windowsize = 100(in x) and Imagesize = 50(in x) locationx = 25
2- Windowsize = 50(in x) and Imagesize = 25(in x) location x = 12.5(~12)
In this case, the windowsize is "something", imagesize is 1/2 of "something" and locationx of the image is 1/4 of something. I wan't to do something like this but with every element inside the frame.
Thanks!
You can use the <ViewBox/> control which does exactly that.
<Page>...
<Grid>
<ViewBox HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<StackPanel>
<Button Content="test" HorizontalAlignment="Left"/>
<TextBlock Text="some text"/>
</StackPanel>
</ViewBox>
</Grid>
</Page>
In the example below, the ViewBox is placed inside a Grid and stretched both Horizontally and Vertically which causes it to stretch with the Page resize.
Reference docs here.

Scrolling problems when using ScaleTransform in Panorama/Pivot/RadSlideView item

I'm trying to implement zoom-functionality in a RadSlideView ItemTemplate. I'm doing this by using a ViewportControl with a Canvas and then applying a RenderTransform (ScaleTransform) to a StackPanel in the Canvas. Similar to the SDK-sample found here.
The problem I have is that the ScaleTransform seems to be affecting the swipe-gesture used to change item in the SlideView/Panorama/Pivot control. E.g. if the ScaleTransform is set to 0.1 it seems like I only need to swipe 1/10th of the length to change item compared to using a ScaleTransform of 1.0.
I found that if I set IsHitTestVisible to false on the ItemTemplate the swiping works like I want. But this is not a solution since I sometimes need to be able to pan the content vertically while still being able to change item by swiping horizontally.
So my question is how can I solve this?
For reference the XAML looks like this:
<Controls:RadSlideView Name="SlideView" ItemsSource="{Binding Pages}" IsLoopingEnabled="False" SelectionChanged="RadSlideView_SelectionChanged" CacheMode="BitmapCache" ManipulationStarted="SlideView_ManipulationStarted" ManipulationCompleted="SlideView_ManipulationCompleted" ManipulationDelta="SlideView_ManipulationDelta">
<Controls:RadSlideView.ItemTemplate>
<DataTemplate>
<ViewportControl x:Name="SlideViewViewport" ViewportChanged="SlideViewViewport_ViewportChanged" Loaded="SlideViewViewport_Loaded">
<Canvas>
<StackPanel>
<Image Source="{Binding Image}" Stretch="Fill" Width="{Binding ElementName=SlideView, Path=DataContext.PageWidth}" Height="{Binding ElementName=SlideView, Path=DataContext.PageHeight}" CacheMode="BitmapCache"/>
<StackPanel.RenderTransform>
<ScaleTransform x:Name="xform"/>
</StackPanel.RenderTransform>
</StackPanel>
</Canvas>
</ViewportControl>
</DataTemplate>
</Controls:RadSlideView.ItemTemplate>
I have also looked at Teleriks RadPanAndZoom-control to avoid implementing my own zoom-functionality, but since I sometimes need to place two pictures side by side and zoom them as if they were one I don't think I can use it.
The problem is that ScaleTransformation scales your picture, but doesn't change it's height and width. Only if Height And Width are overflowing scrollviewer you can scroll the content

How do I get a ScrollViewer with a Rectangle inside to stop scrolling when it reaches the end of the rectangle?

I have created a Rectangle inside of a ScrollViewer like this
<ScrollViewer ManipulationMode="Control" x:Name="songScrollViewer" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Disabled" Height="270" VerticalAlignment="Center" Width="728" Canvas.Top="20" d:LayoutOverrides="HorizontalMargin" >
<Rectangle x:Name="musicBG" Fill="#FF0692FD"/>
</ScrollViewer>
During the use of the app, the size of MusicBg changes, sometimes to something around 3,000 pixels width.
musicBG.Width = _songLength*PixelsPerSecond
However, while scrolling the scrollViewer, it allows me to scroll the rectangle all the way off the screen.
For example this line of code gives me the following values when I have moved the rectangle as far as I want to move it.
if (songScrollViewer.HorizontalOffset > songScrollViewer.ScrollableWidth)
HorizontalOffset has a value of ~1200 and ScrollableWidth has a value of about ~2900.
How can I get this to be done properly so that the rectangle is not scrolled completely off the screen?
I would expect a HorizontalOffset of about 1200 to only push the rectangle about halfway through to it's destination, and not make it start going off screen.
ANSWER:
After much frustration, I was able to solve this problem by using Canvas instead of Border or Rectangle.
I'll award points if anyone can explain why this problem happened, and if there is a less processor intensive control that would work better than canvas.
Edit: Screen shots:
Bad Code:
<ScrollViewer ManipulationMode="Control" x:Name="songScrollViewer" Width="720" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Disabled" Height="270" VerticalAlignment="Top" Canvas.Top="20" HorizontalAlignment="Left" >
<Border x:Name="musicBG" Background="#FF0692FD" VerticalAlignment="Top" HorizontalAlignment="Left" Height="270" />
</ScrollViewer>
Image of bad scroll with bad code:
Good working code:
<ScrollViewer ManipulationMode="Control" x:Name="songScrollViewer" Width="720" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Disabled" Height="270" VerticalAlignment="Top" Canvas.Top="20" HorizontalAlignment="Left" >
<Canvas x:Name="musicBG" Background ="#FF0692FD" Height="270" >
<Border Background="#FF0692FD" VerticalAlignment="Top" HorizontalAlignment="Left" Height="270" />
</Canvas>
</ScrollViewer>
Good Scroll: Notice it says 170 seconds on the bottom right instead of the smaller number of 118 seconds in the bad scroll.
I believe your right, wp7 won't render shapes that are bigger then 2048 pixels. So the reason it's scrolling of the page is because it's treating it as if it were bigger then 2048 but you can only see up to a width of 2048px and its just scrolling over to the "ghost" part of the rectangle.
I'm not sure if you can override this but the best solution I could come up with (without overriding) is by splitting up your rectangle into chucks that are smaller then 2000 (just to be safe) and then displaying them seamlessly in a horizontal stack panel inside the scroll viewer. The problem with this is that depending on how you've coded it, this solution might be hard to implement; but you might just be able to split it in your ViewModel when displaying it and your logic would only see it as one big chunk.

Categories

Resources