Scrollview for Itemscontrol grid - c#

I have a custom control which is basically an itemscontrol.
It's a grid of 3x3 buttons and I want a scrollview to have different pages with every page having 3x3 buttons.
Below are some images to illustrate what I mean:
How would I create something like this?|
The full code of my control can be found here:
Access ItemsControl Items and Animate One by One
Thanks in advance!

You can add a ScrollViewer inside your Grid and have your 3x3 Grid in a horizontally oriented StackPanel.
<Grid >
<ScrollViewer VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Visible" ScrollChanged="ScrollViewer_ScrollChanged">
<StackPanel Orientation="Horizontal" Name="stack">
...
</StackPanel>
</ScrollViewer>
</Grid>
Then use ScrollViewer_ScrollChanged to change the scroll offset
private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
int direction = e.HorizontalChange > 0 ? 1 : -1;
(sender as ScrollViewer).ScrollToHorizontalOffset(stack.ActualWidth * direction / [number of grids]);
}

Related

Irregular behavior - XAML / UWP Community Toolkit Scale animation

Problem : I am using UWP Community Toolkit Scale animation and it works as expected for most of the images in the GridView, but for some the image goes out of bounds . (Please see the image below)
I have detected that the issue happens when the image width is more than 2x (2 times) the height of the image. That is when the image is very wide.
Code
I am using a user control as data template
Xaml :
<!-- Grid View -->
<GridView x:Name="gridView" SelectionChanged="gridView_SelectionChanged">
<GridView.ItemTemplate>
<DataTemplate>
<local:GridViewMenu/>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
<!-- GridViewMenu User Control markup -->
<Grid>
<StackPanel>
<Image Source="{Binding webformatURL}" Stretch="UniformToFill" PointerEntered="image_PointerEntered" PointerExited="image_PointerExited"/>
</StackPanel>
</Grid>
C# Code :
private void image_PointerEntered(object sender, PointerRoutedEventArgs e)
{
Image img = sender as Image;
img.Scale(centerX: (float)(grid.ActualWidth / 2),
centerY: 100,
scaleX: 1.2f,
scaleY: 1.2f,
duration: 500, delay: 0).StartAsync();
}
private void image_PointerExited(object sender, PointerRoutedEventArgs e)
{
Image img = sender as Image;
img.Scale(centerX: (float)(grid.ActualWidth / 2),
centerY: 100,
scaleX: 1f,
scaleY: 1f,
duration: 500, delay: 0).StartAsync();
}
Result (Top left image is not scaling as expected, that is, it is going out of bounds)
How can I solve this issue ?
The scale animation of UWP Community Toolkit package actually use the CompositeTransform class for scaling. According to the description of Transforms and layout section:
Because layout comes first, you'll sometimes get unexpected results if you transform elements that are in a Grid cell or similar layout container that allocates space during layout. The transformed element may appear truncated or obscured because it's trying to draw into an area that didn't calculate the post-transform dimensions when dividing space within its parent container.
So that the parts overflow the bound that being truncated are unexpected. In another words, the image goes out is the transform expected. The current way you are using to meet your requirements is not reliable. If you change width-height ratio of GridViewMenu to 1.0 , you may find more images that width-height ratio larger than 1.0 will go out.
For a solution inside GridView, you could consider to use the ScrollViewer to zoom in the image instead, which can ensure the image is limited in a fixed area. For example:
<Grid x:Name="grid">
<ScrollViewer
x:Name="currentscroll"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden">
<Image
x:Name="myImage"
Width="300"
Height="180"
PointerEntered="image_PointerEntered"
PointerExited="image_PointerExited"
Source="{Binding webformatURL}"
Stretch="UniformToFill">
</Image>
</ScrollViewer>
</Grid>
Code behind:
private void image_PointerEntered(object sender, PointerRoutedEventArgs e)
{
currentscroll.ChangeView(0, 0, 1.2f );
}
private void image_PointerExited(object sender, PointerRoutedEventArgs e)
{
currentscroll.ChangeView(0, 0, 1.0f);
}
You can try to use clipping:
<Grid>
<StackPanel>
<Image Source="{Binding webformatURL}" Stretch="UniformToFill"
PointerEntered="image_PointerEntered"
PointerExited="image_PointerExited">
<Image.Clip>
<RectangleGeometry Rect="0,0,300,150" />
</Image.Clip>
</Image>
</StackPanel>
</Grid>
300 and 150 would be the width and height of the grid item.
Otherwise it looks like a bug in the UWP Community Toolkit, it would be best to report it as an issue on GitHub.

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.

How to make WrapPanel to show vertical scrollbar when children are full with or without ScrollViewer

I have a WrapPanel and buttons are programmatically created and added as children of the WrapPanel. So, I want to show vertical scrollbar when the WrapPanel is full of buttons (children) to be able to add more buttons continuously.
If we need a scrollbar shown, do we have to bring ScrollViewer? Isn't there a way without ScrollViewer? What I want to get is, because the WrapPanel is of small size, I want a scrollbar to be shown only when needed (like full of children).
My code is simple as below (WrapPanel inside Grid and the Grid is inside TabControl)
Many thanks always for your excellence.
Update: I struggled in finding solution on internet for even several days. And I tried put WrapPanel inside ScrollViewer. However, though I set the VerticalScroll to auto, the vertical scrollbar is always shown even when the WrapPanel doesn't have any children.
Furthermore, when I intentionally make the WrapPanel full of children (buttons), the vertical scrollbar of ScrollViewer doesn't provide scrolldown availability.
And the buttons at the bottom line of WrapPanel shown cut and more, I can't scroll down to see beyond the button at the bottom line. I made buttons to be placed beyond the bottom line of WrapPanel intentionally.
With or without, I want the vertical scrollbar to be shown when only needed (full of children). It seems very easy to be done. But it's difficult to make it work properly.
Solution: was provided by Mr. Henk Holterman
<DropShadowEffect/>
</Button.Effect>
</Button>
<WrapPanel x:Name="WrapPanelGreen" HorizontalAlignment="Left" Height="180" VerticalAlignment="Top" Width="232" UseLayoutRounding="True" ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Auto"/>
</Grid>
</TabItem>
</TabControl>
And below is my simple code which make button programmatically and add as a child of WrapPanel.
for (int k = 0; k < Overviews.Length; k++)
{
Button btnoverviewcontent = new Button();
ToolTip tt = new ToolTip();
tt.Content = "Press this button if you want to modify or delete.";
btnoverviewcontent.ToolTip = tt;
btnoverviewcontent.Cursor = Cursors.Hand;
SolidColorBrush mySolidColorBrush = new SolidColorBrush();
mySolidColorBrush.Color = Color.FromArgb(255, 101, 173, 241);
btnoverviewcontent.Background = mySolidColorBrush;
btnoverviewcontent.Effect = new DropShadowEffect
{
Color = new Color { A = 255, R = 0, G = 0, B = 0 },
Direction = 315,
ShadowDepth = 5,
Opacity = 1
};
btnoverviewcontent.Padding = new Thickness(3, 3, 3, 3);
btnoverviewcontent.HorizontalContentAlignment = System.Windows.HorizontalAlignment.Stretch;
TextBlock textBlock = new TextBlock()
{
Text = Overviews[k],
TextAlignment = TextAlignment.Left,
TextWrapping = TextWrapping.Wrap,
};
btnoverviewcontent.Content = textBlock;
btnoverviewcontent.BorderThickness = new Thickness(0, 0, 0, 0);
btnoverviewcontent.FontStretch = FontStretches.UltraExpanded;
btnoverviewcontent.Margin = new Thickness(5, 5, 5, 5);
WrapPanelGreen.Children.Add(btnoverviewcontent);
btnoverviewcontent.Click += new RoutedEventHandler(OnOverviewClick);
The idea in WPF is that every component has only its own job and if you want certain behavior, you combine multiple components to create the view you are looking for.
This means that in order to get a scroll bar for a panel, you will have to wrap it in a ScrollViewer component. That’s the purpose of the ScrollViewer and that’s the only (sane) solution to solve this.
However, though I set the verticalscroll to auto, the verticalscrollbar is always shown even when the Wrappanel doesn't have any child […]
Then you seem to be using the ScrollViewer incorrectly, or wrapping the wrong element. It should look like this:
<ScrollViewer VerticalScrollBarVisibility="Auto">
<WrapPanel>
<!-- Any number of components here -->
</WrapPanel>
</ScrollViewer>
If I place lots of example labels inside that, then I do get a scroll bar as soon as the window is not large enough to show them all. But if there is enough room, the scroll bar is not displayed.
Note that the ScrollViewer itself needs to have the proper dimensions in the parent element, so make sure that it’s not larger than the visible area. It is also necessary for the WrapPanel (or whatever other element you wrap with the ScrollViewer) to have auto widths and heights. Otherwise, with fixed dimensions, the dimensions of the panel will not change as you modify the panel’s content and as such the scrolling status will not change.
See this complete example with a dynamic number of elements:
<Window x:Class="WpfExampleApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="200">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<WrapPanel Name="panel">
<Button Click="Button_Click">Add child</Button>
</WrapPanel>
</ScrollViewer>
</Window>
Code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Label element = new Label() { Content = "This is some example content" };
panel.Children.Add(element);
}
}
...just in case someone is looking at this post after 4.5 years and feels, that the solution doesn't help at all:
I had a similar problem, which was caused by a StackPanel into which my ScrollViewer and dynamic buttons where put in...
Seems like the StackPanel expands the height limitless, so that the ScrollViewer and the WrapPanel always expand their height past the window-bounds. so you couldn't scroll at all. Wrapping that StackPanel into a ScrollViewer helped in my case (although eventually i had to change the controls in the end...)
regards

How to get the ScrollViewer's VerticalOffset in dev independent pixels for an ItemsControl with CanContentScroll true

I have a listbox with CanContentScroll true, but others that are false.
And i'm writing a behavior that needs to extract the scrollviewer from it and calculate the vertical scroll offset in device independent pixels.
Since the CanContentScroll can be either true or false I sometimes get logical item units while other times physical pixels.
So I need to calculate the pixel values in case CanContentScroll is true.
For an example: when the listbox is scrolled by three items VerticalOffset will give 3. How to convert this 3 to the vertical pixels used by the items (which can vary in size)?
Thanks
You cannot calculate the values in pixels without effectively setting CanContentScroll="False".
To know the size in pixels you would need to create the containers of all the items and sum up the heights of all the containers. To do that you will need to generate all the containers first. Which would mean that you effectively lost virtualization and effectively set CanContentScroll="False". In that case why use CanContentScroll="True" in the first place?
What Nikolay's code tries to do is take on the burden of doing yourself what CanContentScroll="False" does without giving you the smooth scrolling that you would have otherwise gained.
More importantly what problem does the physical offset solve that you cannot solve with the logical offset if you know that CanContentScroll="true" always?
You can just sum sizes of scrolled items like this:
private void Button_Click(object sender, RoutedEventArgs e)
{
var scroller = GetScrollViewer();
var listBox = GetListBox();
double trueOffset;
if (scroller.CanContentScroll)
for (int i = 0; i < (int)scroller.VerticalOffset; i++)
{
var listBoxItem = (FrameworkElement)listBox.ItemContainerGenerator.ContainerFromIndex(i);
trueOffset += listBoxItem.ActualHeight;
}
else
trueOffset = scroller.VerticalOffset;
this.Title = trueOffset.ToString();
}
This simple code works well for listbox with variable size items
I would try a different approch.
Embed your listbox with a Scrollviewer and disable the scrolling behaviour of the listbox itself.
You can than can measure the scrollviewer VerticalOffset outside the listbox.
<TextBlock Grid.Row="0"
Text="{Binding ElementName=scrollviewer, Path=VerticalOffset}"/>
<ScrollViewer x:Name="scrollviewer" VerticalScrollBarVisibility="Auto" Grid.Row="1">
<ListBox ScrollViewer.CanContentScroll="False">
<ListBox.Items>
<ListBoxItem>A</ListBoxItem>
<ListBoxItem>B</ListBoxItem>
<ListBoxItem>C</ListBoxItem>

WPF databinding problem

I have a grid inside a canvas on a tab.
The grid contains a large bitmap image,
I have(tried to) bound the size of the grid to the size of the tab and also have a five pixel margin around the grid.
imageTab.cs
public ImageTab(SendInfo sendInfo, int numImge, int numAccs)
{
imageDisplay = new ImageDisplay(sendInfo, numImge, numAccs);
imageDisplay.ClipToBounds = true;
CreateCanvas();
}
private void CreateCanvas()
{
Canvas canvas = new Canvas();
canvas.Children.Add(imageDisplay);
this.AddChild(canvas);
}
ImageDisplay.xaml
<UserControl x:Class="MyProj.ImageDisplay">
<Grid Margin="5,5,5,5" Height="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=TabControl, AncestorLevel=1}, Path=ActualHeight}">
<Image/>
</Grid>
</UserControl>
The grid comes off the bottom of the tab area slightly causing the bottom of the image to be cut off.
Is there a problem with my databinding, do I need to apply some sort of offset to it? (size of tab - 10pixels for the margin?)
You don't need to set the Height property at all (also realize that it is incorrect to do so as you have it when you consider the 5 pixel margin, i.e., it would be off by 10 pixels).
Just leave VerticalAlignment and HorizontalAlignment at their default values (which is Stretch) to get the effect you are after here.
Try this on a new Window to see what I mean:
<Window x:Class="WpfApplication9.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="438" Width="587" Background="Pink">
<Grid Background="Black" Margin="5">
</Grid>
</Window>
The grid here will be black and will always stretch to the size of the window, using a 5 pixel margin which you will see because the Window's back color is pink.

Categories

Resources