I am developing an UWP app. I used a ProgressRing as below code:
<ListViewItem Template="{ThemeResource SeetingsListItemContentThemeStyle}"
BorderBrush="{ThemeResource SettingsListItemBorderThemeColor}"
BorderThickness="0,0,0,1"
AutomationProperties.SizeOfSet="0"
AutomationProperties.Name="{Binding SettingsAutomationText, Mode=OneWay}">
<ProgressRing VerticalAlignment="Center"
Margin="18, 0, 0, 14"
Height="21"
Width="21"
IsActive="True"
Foreground="Red"
Visibility="Visible"/>
</ListViewItem>
"SeetingsListItemContentThemeStyle" is as below:
For Light Theme:
<ControlTemplate x:Key="SeetingsListItemContentThemeStyle" TargetType="ListViewItem">
<ListViewItemPresenter x:Name="Root"
CheckBrush="{ThemeResource ListViewItemCheckBrush}"
ContentMargin="{TemplateBinding Padding}"
CheckBoxBrush="{ThemeResource ListViewItemCheckBoxBrush}"
CheckMode="{ThemeResource ListViewItemCheckMode}"
FocusBorderBrush="{ThemeResource ListViewItemFocusBorderBrush}"
FocusSecondaryBorderBrush="{ThemeResource ListViewItemFocusSecondaryBorderBrush}"
HorizontalContentAlignment="Left"
Control.IsTemplateFocusTarget="False"
PressedBackground="#12000000"
PlaceholderBackground="{ThemeResource ListViewItemPlaceholderBackground}"
PointerOverBackground="#08000000"
SelectionCheckMarkVisualEnabled="{ThemeResource ListViewItemSelectionCheckMarkVisualEnabled}"
SelectedPressedBackground="#12000000"
SelectedPointerOverBackground="#08000000"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}">
</ListViewItemPresenter>
</ControlTemplate>
For Dark Theme:
<ControlTemplate x:Key="SeetingsListItemContentThemeStyle" TargetType="ListViewItem">
<ListViewItemPresenter x:Name="Root"
CheckBrush="{ThemeResource ListViewItemCheckBrush}"
ContentMargin="{TemplateBinding Padding}"
CheckBoxBrush="{ThemeResource ListViewItemCheckBoxBrush}"
CheckMode="{ThemeResource ListViewItemCheckMode}"
FocusBorderBrush="{ThemeResource ListViewItemFocusBorderBrush}"
FocusSecondaryBorderBrush="{ThemeResource ListViewItemFocusSecondaryBorderBrush}"
HorizontalContentAlignment="Left"
Control.IsTemplateFocusTarget="False"
PressedBackground="#12ffffff"
PlaceholderBackground="{ThemeResource ListViewItemPlaceholderBackground}"
PointerOverBackground="#08ffffff"
SelectionCheckMarkVisualEnabled="{ThemeResource ListViewItemSelectionCheckMarkVisualEnabled}"
SelectedPressedBackground="#12ffffff"
SelectedPointerOverBackground="#08ffffff"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}">
</ListViewItemPresenter>
</ControlTemplate>
The progress ring is showing correctly when the parent page loads. But it is not showing when I change theme from System. Once this issue occurs, Progress Ring not shows for Light-Dark both themes. If I go back to some other pages and come back to this page again, this time progress ring shows correctly.
Can anyone please help me to work Progress Ring correctly after theme change in app runtime?
ProgressRing not showing when Theme changed in runtime
I think the problem is the ProgressRing ring animation was be destroy when you change ListViewItem's ControlTemplate by update current system theme. For this scenario, you could reload current page to rebuild this animation after the theme change.
private void Button_Click(object sender, RoutedEventArgs e)
{
Reload();
}
public bool Reload() { return Reload(null); }
private bool Reload(object param)
{
Type type = this.Frame.CurrentSourcePageType;
if (this.Frame.BackStack.Any())
{
type = this.Frame.BackStack.Last().SourcePageType;
param = this.Frame.BackStack.Last().Parameter;
}
try { return this.Frame.Navigate(type, param); }
finally { this.Frame.BackStack.Remove(this.Frame.BackStack.Last()); }
}
Usage
public MainPage()
{
this.InitializeComponent();
var Listener = new ThemeListener();
Listener.ThemeChanged += Listener_ThemeChanged;
}
private void Listener_ThemeChanged(ThemeListener sender)
{
Reload();
}
Related
I have two buttons placed horizontally. They are inside a grid. This grid containing two buttons has width 370. When the text on the button becomes large, it needs width more than 370. So what I want to do is, instead of placing then horizontally, I want to place them vertically dynamically when text will start cropping. Basically, I want auto-reflow behavior inside this grid for these two buttons based on width of the grid (not based on width of main window). So I want them to fit in 370 width and if they cannot, I want them to place themselves vertically. How can I achieve this?
I explored GridView but it will show buttons inside the box that comes with GridView so I do not want extra UI that comes with GridView unless we have an option to hide it?
I checked AdaptiveTrigger but that is based on width of window not of the control (grid in this case)
<Grid
Grid.Row="2"
Margin="0,36,0,28"
Width="370"
HorizontalAlignment="Left"
VerticalAlignment="Bottom">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid
Grid.Column="0"
CornerRadius="3">
<Button
MinWidth="118"
MinHeight="30"
HorizontalAlignment="Left"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
AutomationProperties.Name="{x:Bind ViewModel.PrimaryActionAutomationName, Mode=OneWay}"
BorderThickness="1"
Click="{x:Bind ViewModel.InvokePrimaryAction}"
Content="{x:Bind ViewModel.PrimaryAction, Mode=OneWay}"
CornerRadius="3"
Style="{StaticResource AccentButtonStyle}" />
</Grid>
<Grid
Grid.Column="1"
CornerRadius="3"
Margin="32,0,0,0">
<HyperlinkButton
AutomationProperties.Name="{x:Bind ViewModel.SecondaryLinkAutomationName, Mode=OneWay}"
AutomationProperties.AutomationId="{Binding AutomationId, ConverterParameter=HyperlinkButton, Converter={StaticResource AutomationIdConverter}}"
Content="{x:Bind ViewModel.SecondaryText, Mode=OneWay}"
FontSize="14"
Margin="0,0,0,0"
Style="{StaticResource HyperlinkButtonStyle}"
NavigateUri="{x:Bind ViewModel.SecondaryLink, Mode=OneWay}" />
</Grid>
</Grid>
For your scenario, I'd suggest you custom a StateTrigger based on the StateTriggerBase Class to monitor the width of the Grid and apply visual states based on the width.
I've made a simple sample that you could refer to.
ControlSizeTrigger:
public class ControlSizeTrigger : StateTriggerBase
{
//private variables
private double _minHeight, _minWidth = -1;
private FrameworkElement _targetElement;
private double _currentHeight, _currentWidth;
//public properties to set from XAML
public double MinHeight
{
get
{
return _minHeight;
}
set
{
_minHeight = value;
}
}
public double MinWidth
{
get
{
return _minWidth;
}
set
{
_minWidth = value;
}
}
public FrameworkElement TargetElement
{
get
{
return _targetElement;
}
set
{
if (_targetElement != null)
{
_targetElement.SizeChanged -= _targetElement_SizeChanged;
}
_targetElement = value;
_targetElement.SizeChanged += _targetElement_SizeChanged;
}
}
//Handle event to get current values
private void _targetElement_SizeChanged(object sender, SizeChangedEventArgs e)
{
_currentHeight = e.NewSize.Height;
_currentWidth = e.NewSize.Width;
UpdateTrigger();
}
//Logic to evaluate and apply trigger value
private void UpdateTrigger()
{
//if target is set and either minHeight or minWidth is set, proceed
if (_targetElement != null && (_minWidth > 0 || _minHeight > 0))
{
//if both minHeight and minWidth are set, then both conditions must be satisfied
if (_minHeight > 0 && _minWidth > 0)
{
SetActive((_currentHeight >= _minHeight) && (_currentWidth >= _minWidth));
}
//if only one of them is set, then only that condition needs to be satisfied
else if (_minHeight > 0)
{
SetActive(_currentHeight >= _minHeight);
}
else
{
SetActive(_currentWidth >= _minWidth);
bool bbb = _currentWidth >= _minWidth;
Debug.WriteLine("Widthtrigger :" + bbb);
}
}
else
{
SetActive(false);
}
}
}
MainPage.xaml:
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ControlSizeStates">
<VisualState x:Name="SizeChange">
<VisualState.StateTriggers>
<local:ControlSizeTrigger MinWidth="800" TargetElement="{x:Bind Path=MyStackPanel}" />
<!--<AdaptiveTrigger MinWindowHeight="500" />-->
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="MyStackPanel.Background" Value="Red" />
<Setter Target="MyStackPanel.Orientation" Value="Horizontal" />
<Setter Target="MyButton.Foreground" Value="Black" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<StackPanel x:Name="MyStackPanel" Width="100" Height="200" Background="AliceBlue" Orientation="Vertical">
<Button x:Name="MyButton" Foreground="Red" Content="Click" Click="Button_Click"/>
<Button x:Name="MyButton2" Foreground="Red" Content="Click" />
</StackPanel>
</Grid>
MainPage.cs:
public MainPage()
{
this.InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MyStackPanel.Width = 1100;
}
I want to show a collection of images with entry animations (opacity, translation and rescaling) in a UWP app.
How do I apply unique storyboards to each of these images within the ItemsControl.ItemTemplate DataTemplate so that each image has unique translations and sizes? While the animations are the same, their parameters do change from image to image.
Have been looking for several days on something that works reliably, and all I have found so far is stuff that will not work in UWP apps.
How do I apply unique storyboards to each of these images
Firstly, since you want each of these images has a different animation, I suggest to have a property for the image instance to indicate which animation the image will play.
Then secondly, we need to resolve the issue that with different property value to trigger different Storyboard. For this we could consider to use DataTriggerBehavior of XamlBehaviors package. Install the XamlBehaviors Nuget package through this link.
At last binding the corresponding Storyboard to the trigger it will work.
The following is a completed working demo you could have a test.
XAML
...
xmlns:Interactions="using:Microsoft.Xaml.Interactions.Core"
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:Media="using:Microsoft.Xaml.Interactions.Media"
...
<ItemsControl x:Name="itemcontrol">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.Resources>
<Storyboard x:Name="StoryBoardOpacity">
<DoubleAnimation
Storyboard.TargetName="img"
Storyboard.TargetProperty="Opacity"
From="0.0"
To="1.0"
Duration="0:0:5" />
</Storyboard>
<Storyboard x:Name="StoryboardScale">
<DoubleAnimation
Storyboard.TargetName="transform"
Storyboard.TargetProperty="ScaleX"
From="1.0"
To="2.0"
Duration="0:0:5" />
</Storyboard>
</Grid.Resources>
<Image
x:Name="img"
Width="100"
Height="100"
Source="{Binding imageurl}">
<Interactivity:Interaction.Behaviors>
<Interactions:DataTriggerBehavior
Binding="{Binding storyboard}"
ComparisonCondition="Equal"
Value="Opacity">
<Media:ControlStoryboardAction Storyboard="{StaticResource StoryBoardOpacity}" />
</Interactions:DataTriggerBehavior>
<Interactions:DataTriggerBehavior
Binding="{Binding storyboard}"
ComparisonCondition="Equal"
Value="Scale">
<Media:ControlStoryboardAction Storyboard="{StaticResource StoryboardScale}" />
</Interactions:DataTriggerBehavior>
</Interactivity:Interaction.Behaviors>
<Image.RenderTransform>
<CompositeTransform x:Name="transform" />
</Image.RenderTransform>
</Image>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Code behind
private ObservableCollection<OneImage> imageSources;
public MainPage()
{
this.InitializeComponent();
imageSources = new ObservableCollection<OneImage>()
{
new OneImage()
{
imageurl="ms-appx:///Assets/caffe1.jpg",
storyboard="Opacity"
},
new OneImage()
{
imageurl="ms-appx:///Assets/caffe2.jpg",
storyboard="Opacity"
},
new OneImage()
{
imageurl="ms-appx:///Assets/caffe3.jpg",
storyboard="Scale"
}
};
itemcontrol.ItemsSource = imageSources;
}
public class OneImage
{
public string imageurl { get; set; }
public string storyboard { get; set; }
}
I am trying to make a typical Radio Button Style for Windows 10 SplitView Control - With an Icon and text. I tried TemplateBinding the UriSource property to RadioButton's Tag property. But the problem here is that Tag is a string type and UriSource is Uri. So it is not working. Is there a way to Set the UriSource in some other way?
Relevant Snippet of the Style:
<Grid Name="BackgroundGrid" Background="Transparent" VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="48"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<BitmapIcon Height="38" UriSource="{TemplateBinding Tag}" Margin="5,8,5,5" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<ContentPresenter x:Name="ContentPresenter" AutomationProperties.AccessibilityView="Raw" ContentTemplate="{TemplateBinding ContentTemplate}" ContentTransitions="{TemplateBinding ContentTransitions}" Content="{TemplateBinding Content}" Grid.Column="1" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" TextWrapping="Wrap" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
And the RadioButton:
<RadioButton Style="{StaticResource SplitViewRadioButtonStyle}" Content="Home" x:Name="Home" Tag="/Assets/Home.png"/>
How about an attached property?
I like this approach over converters 'cause you can attach it to any control you want and it has better performance.
Attached Property
public class Properties
{
public static Uri GetIconUri(DependencyObject obj)
{
return (Uri)obj.GetValue(IconUriProperty);
}
public static void SetIconUri(DependencyObject obj, Uri value)
{
obj.SetValue(IconUriProperty, value);
}
public static readonly DependencyProperty IconUriProperty =
DependencyProperty.RegisterAttached("IconUri", typeof(Uri), typeof(Properties), new PropertyMetadata(null));
}
Binding inside Style
<BitmapIcon UriSource="{Binding Path=(local:Properties.IconUri), RelativeSource={RelativeSource TemplatedParent}}" ...
Usage
<RadioButton local:Properties.IconUri="Assets/Home.png" ...
Simplest way would be to add ms-appx/// as appears to a resource in the app
Tag="ms-appx///Assets/Home.png"
So I worked up a solution by extending the RadioButton Control and Adding two properties i.e., IconUri and IconBitmap.
public static readonly DependencyProperty IconUriProperty = DependencyProperty.Register("IconUri", typeof(Uri), typeof(CustomRadioButton), new PropertyMetadata(null, OnIconBitmapChanged));
private static void OnIconBitmapChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ImageRadioButton current = d as CustomRadioButton;
if(current!=null)
{
current.IconBitmap = new BitmapImage(current.IconUri);
}
}
public Uri IconUri
{
get { return (Uri)GetValue(IconUriProperty); }
set { SetValue(IconUriProperty, value); }
}
public static readonly DependencyProperty IconBitmapProperty = DependencyProperty.Register("IconBitmap", typeof(BitmapImage), typeof(CustomRadioButton), new PropertyMetadata(null));
public BitmapImage IconBitmap
{
get { return (BitmapImage)GetValue(IconBitmapProperty); }
set { SetValue(IconBitmapProperty, value); }
}
And then Binding the Image Control in the Style to IconBitmap. Since, here we are binding to the BitmapImage directly, it doesn't fail. Also works with Web URIs.
Xaml for RadioButton:
<templatedControls:CustomRadioButton IconUri="uri" Style="{StaticResource CustomRadioButtonStyle}"/>
And in its style:
<Image Source="{TemplateBinding IconBitmap}" />
To make the style work with your Extended Radio Button just replace 'Target' of the style from RadioButton to templatedControls:CustomRadioButton, where templatedControls is the XAML namespace which has the CustomRadioButton.
I have a Image and I want crop it by using a rectangle, code below is the code I put a image and draw a rectangle at middle of the image:
MainPage.Xaml:
<Canvas x:Name="canvas" HorizontalAlignment="Center" VerticalAlignment="Center" Width="340" Height="480" Background="Blue">
<Image x:Name="photo" HorizontalAlignment="Center" VerticalAlignment="Center" ManipulationMode="All">
<Image.RenderTransform>
<CompositeTransform/>
</Image.RenderTransform>
</Image>
<Path Stroke="Black" StrokeThickness="1">
<Path.Data>
<RectangleGeometry Rect="0,0,340,480"/>
</Path.Data>
</Path>
</Canvas>
I successful display the image and draw a rectangle. The sample image as below:
Now I want click a button to crop the image within the rectangle(not auto clip). The rectangle is auto added when image loaded. So cannot use "Point Pressed" and "Point Released". And also cannot use "rectangle.clip" because it will auto clip the image. How do I solve it? Thanks
Updated:
I able to move the image,How do I bind the data and set the rectangle coordinate to dynamic? Code below is to transform the image. Thanks.
public sealed partial class MainPage: Page
{
private CompositeTransform compositeTranslation;
public MainPage()
{
this.InitializeComponent();
photo.ManipulationDelta += Composite_ManipulationDelta;
compositeTranslation = new CompositeTransform();
photo.RenderTransform = this.compositeTranslation;
}
void Composite_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
// scale the image.
compositeTranslation.CenterX = photo.ActualWidth / 2;
compositeTranslation.CenterY = photo.ActualHeight / 2;
compositeTranslation.ScaleX *= e.Delta.Scale;
compositeTranslation.ScaleY *= e.Delta.Scale;
compositeTranslation.TranslateX += e.Delta.Translation.X;
compositeTranslation.TranslateY += e.Delta.Translation.Y;
}
}
I have not used or XAML as it creates confusion for me. So I created a snippet according to you problem. Try that and let me know the results. I have used the same image as you posted
XAML
<Page.BottomAppBar>
<AppBar IsSticky="True" IsOpen="True">
<Button Content="Crop" Click="btnCrop_Click" />
</AppBar>
</Page.BottomAppBar>
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Image x:Name="photo" HorizontalAlignment="Center" VerticalAlignment="Center" ManipulationMode="All" Source="http://i.stack.imgur.com/UIBSp.png" />
<Path x:Name="path" Stroke="Red" StrokeThickness="3">
<Path.Data>
<RectangleGeometry Rect="545,212,440,420"/>
</Path.Data>
</Path>
</Grid>
C#
private void btnCrop_Click(object sender, RoutedEventArgs e)
{
var _rect = new RectangleGeometry();
_rect.Rect = path.Data.Bounds;
photo.Clip = _rect;
}
I'm trying to create a similar experience as in the ScrollViewerSample from the Windows 8 SDK samples to be able to snap to the items inside a ScrollViewer when scrolling left and right. The implementation from the sample (which works) is like this:
<ScrollViewer x:Name="scrollViewer" Width="480" Height="270"
HorizontalAlignment="Left" VerticalAlignment="Top"
VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto"
ZoomMode="Disabled" HorizontalSnapPointsType="Mandatory">
<StackPanel Orientation="Horizontal">
<Image Width="480" Height="270" AutomationProperties.Name="Image of a cliff" Source="images/cliff.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image Width="480" Height="270" AutomationProperties.Name="Image of Grapes" Source="images/grapes.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image Width="480" Height="270" AutomationProperties.Name="Image of Mount Rainier" Source="images/Rainier.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image Width="480" Height="270" AutomationProperties.Name="Image of a sunset" Source="images/sunset.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image Width="480" Height="270" AutomationProperties.Name="Image of a valley" Source="images/valley.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
</StackPanel>
</ScrollViewer>
The only difference with my desired implementation is that I don't want a StackPanel with items inside, but something I can bind to. I am trying to accomplish this with an ItemsControl, but for some reason the Snap behavior does not kick in:
<ScrollViewer x:Name="scrollViewer" Width="480" Height="270"
HorizontalAlignment="Left" VerticalAlignment="Top"
VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto"
ZoomMode="Disabled" HorizontalSnapPointsType="Mandatory">
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<Image Width="480" Height="270" AutomationProperties.Name="Image of a cliff" Source="images/cliff.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image Width="480" Height="270" AutomationProperties.Name="Image of Grapes" Source="images/grapes.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image Width="480" Height="270" AutomationProperties.Name="Image of Mount Rainier" Source="images/Rainier.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image Width="480" Height="270" AutomationProperties.Name="Image of a sunset" Source="images/sunset.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image Width="480" Height="270" AutomationProperties.Name="Image of a valley" Source="images/valley.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
</ItemsControl>
</ScrollViewer>
Suggestions would be greatly appreciated!
Thanks to Denis, I ended up using the following Style on the ItemsControl and removed the ScrollViewer and inline ItemsPanelTemplate altogether:
<Style x:Key="ItemsControlStyle" TargetType="ItemsControl">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ItemsControl">
<ScrollViewer Style="{StaticResource HorizontalScrollViewerStyle}" HorizontalSnapPointsType="Mandatory">
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Getting snap points to work for bound collections can be tricky. For snap points to work immediate child of ScrollViewer should implement IScrollSnapPointsInfo interface. ItemsControl doesn't implement IScrollSnapPointsInfo and consequently you wouldn't see snapping behaviour.
To work around this issue you got couple options:
Create custom class derived from ItemsControl and implement IScrollSnapPointsInfo interface.
Create custom style for items control and set HorizontalSnapPointsType property on ScrollViewer inside the style.
I've implemented former approach and can confirm that it works, but in your case custom style could be a better choice.
Ok, here is the simplest (and standalone) example for horizontal ListView with binded items and correctly working snapping (see comments in following code).
xaml:
<ListView x:Name="YourListView"
ItemsSource="{x:Bind Path=Items}"
Loaded="YourListView_OnLoaded">
<!--Set items panel to horizontal-->
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<!--Some item template-->
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
background code:
private void YourListView_OnLoaded(object sender, RoutedEventArgs e)
{
//get ListView
var yourList = sender as ListView;
//*** yourList style-based changes ***
//see Style here https://msdn.microsoft.com/en-us/library/windows/apps/mt299137.aspx
//** Change orientation of scrollviewer (name in the Style "ScrollViewer") **
//1. get scrollviewer (child element of yourList)
var sv = GetFirstChildDependencyObjectOfType<ScrollViewer>(yourList);
//2. enable ScrollViewer horizontal scrolling
sv.HorizontalScrollMode =ScrollMode.Auto;
sv.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
sv.IsHorizontalRailEnabled = true;
//3. disable ScrollViewer vertical scrolling
sv.VerticalScrollMode = ScrollMode.Disabled;
sv.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
sv.IsVerticalRailEnabled = false;
// //no we have horizontally scrolling ListView
//** Enable snapping **
sv.HorizontalSnapPointsType = SnapPointsType.MandatorySingle; //or you can use SnapPointsType.Mandatory
sv.HorizontalSnapPointsAlignment = SnapPointsAlignment.Near; //example works only for Near case, for other there should be some changes
// //no we have horizontally scrolling ListView with snapping and "scroll last item into view" bug (about bug see here http://stackoverflow.com/questions/11084493/snapping-scrollviewer-in-windows-8-metro-in-wide-screens-not-snapping-to-the-las)
//** fix "scroll last item into view" bug **
//1. Get items presenter (child element of yourList)
var ip = GetFirstChildDependencyObjectOfType<ItemsPresenter>(yourList);
// or var ip = GetFirstChildDependencyObjectOfType<ItemsPresenter>(sv); //also will work here
//2. Subscribe to its SizeChanged event
ip.SizeChanged += ip_SizeChanged;
//3. see the continuation in: private void ip_SizeChanged(object sender, SizeChangedEventArgs e)
}
public static T GetFirstChildDependencyObjectOfType<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj is T) return depObj as T;
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = GetFirstChildDependencyObjectOfType<T>(child);
if (result != null) return result;
}
return null;
}
private void ip_SizeChanged(object sender, SizeChangedEventArgs e)
{
//3.0 if rev size is same as new - do nothing
//here should be one more condition added by && but it is a little bit complicated and rare, so it is omitted.
//The condition is: yourList.Items.Last() must be equal to (yourList.Items.Last() used on previous call of ip_SizeChanged)
if (e.PreviousSize.Equals(e.NewSize)) return;
//3.1 get sender as our ItemsPresenter
var ip = sender as ItemsPresenter;
//3.2 get the ItemsPresenter parent to get "viewable" width of ItemsPresenter that is ActualWidth of the Scrollviewer (it is scrollviewer actually, but we need just its ActualWidth so - as FrameworkElement is used)
var sv = ip.Parent as FrameworkElement;
//3.3 get parent ListView to be able to get elements Containers
var yourList = GetParent<ListView>(ip);
//3.4 get last item ActualWidth
var lastItem = yourList.Items.Last();
var lastItemContainerObject = yourList.ContainerFromItem(lastItem);
var lastItemContainer = lastItemContainerObject as FrameworkElement;
if (lastItemContainer == null)
{
//NO lastItemContainer YET, wait for next call
return;
}
var lastItemWidth = lastItemContainer.ActualWidth;
//3.5 get margin fix value
var rightMarginFixValue = sv.ActualWidth - lastItemWidth;
//3.6. fix "scroll last item into view" bug
ip.Margin = new Thickness(ip.Margin.Left,
ip.Margin.Top,
ip.Margin.Right + rightMarginFixValue, //APPLY FIX
ip.Margin.Bottom);
}
public static T GetParent<T>(DependencyObject reference) where T : class
{
var depObj = VisualTreeHelper.GetParent(reference);
if (depObj == null) return (T)null;
while (true)
{
var depClass = depObj as T;
if (depClass != null) return depClass;
depObj = VisualTreeHelper.GetParent(depObj);
if (depObj == null) return (T)null;
}
}
About this example.
Most of checks and errors handling is omitted.
If you override ListView Style/Template, VisualTree search parts must be changed accordingly
I'd rather create inherited from ListView control with this logic, than use provided example as-is in real code.
Same code works for Vertical case (or both) with small changes.
Mentioned snapping bug - ScrollViewer bug of handling SnapPointsType.MandatorySingle and SnapPointsType.Mandatory cases. It appears for items with not-fixed sizes
.