Answered by:
[UWP]Mail style ListView

Question
-
I'm in the process of making my first UWP App in XAML/C# (have made others in HTML/JS) so I am in a quite early learning process. But I am trying to reproduce a similar ListView to the Mail app that comes with Windows 10, I like the functionality that you can slide the list items to the left or to the right to perform an action (Archive/Flag). I was wondering if anyone knows of any resources that can help to me to replicate the same thing, or any tips where to start?
Thanks!
Andy
- Edited by Andy Ardener Wednesday, February 10, 2016 1:15 PM
- Edited by Amy PengMicrosoft employee Thursday, February 11, 2016 2:27 PM add tag
Wednesday, February 10, 2016 1:07 PM
Answers
-
Hi,
Really interesting question. I don't know that I have the full answer for you but maybe I can help a little.
I wondered whether the best way to do this was to create a custom ListView or a custom ListViewItem or maybe re-remplate the ListViewItem.
I didn't have a lot of success with those so I knocked up the starting point for a control which displays 3 pieces of content;
- the main content
- content to be displayed as the main content swipes left
- content to be displayed as the main content swipes right
and the control raises a Swiped event (with the direction) when it detects a swipe of a 'significant' size.
The code for my control ended up looking like this (I didn't spend a huge amount of time on it - it's just a starting point);
using System; using Windows.Foundation; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Markup; using Windows.UI.Xaml.Media; // these names line up with visual states in generic.xaml so // rename at your peril. enum SwipeDirection { Left, Right, Default }; class SwipeEventArgs : EventArgs { public SwipeDirection Direction { get; set; } } [ContentProperty(Name = "Content")] class SwipeContentControl : Control { public event EventHandler<SwipeEventArgs> Swiped; public static DependencyProperty LeftContentProperty = DependencyProperty.Register( "LeftContent", typeof(object), typeof(SwipeContentControl), null); public static DependencyProperty RightContentProperty = DependencyProperty.Register( "RightContent", typeof(object), typeof(SwipeContentControl), null); public static DependencyProperty ContentProperty = DependencyProperty.Register( "Content", typeof(object), typeof(SwipeContentControl), null); public static DependencyProperty LeftContentTemplateProperty = DependencyProperty.Register( "LeftContentTemplate", typeof(DataTemplate), typeof(SwipeContentControl), null); public static DependencyProperty RightContentTemplateProperty = DependencyProperty.Register( "RightContentTemplate", typeof(DataTemplate), typeof(SwipeContentControl), null); public static DependencyProperty ContentTemplateProperty = DependencyProperty.Register( "ContentTemplate", typeof(DataTemplate), typeof(SwipeContentControl), null); public object LeftContent { get { return (base.GetValue(LeftContentProperty)); } set { base.SetValue(LeftContentProperty, value); } } public DataTemplate LeftContentTemplate { get { return ((DataTemplate)base.GetValue(LeftContentTemplateProperty)); } set { base.SetValue(LeftContentTemplateProperty, value); } } public DataTemplate RightContentTemplate { get { return ((DataTemplate)base.GetValue(RightContentTemplateProperty)); } set { base.SetValue(RightContentTemplateProperty, value); } } public DataTemplate ContentTemplate { get { return ((DataTemplate)base.GetValue(ContentTemplateProperty)); } set { base.SetValue(ContentTemplateProperty, value); } } public object RightContent { get { return (base.GetValue(RightContentProperty)); } set { base.SetValue(RightContentProperty, value); } } public object Content { get { return (base.GetValue(ContentProperty)); } set { base.SetValue(ContentProperty, value); } } public SwipeContentControl() { this.DefaultStyleKey = typeof(SwipeContentControl); this.ManipulationMode = ManipulationModes.TranslateX; this.ManipulationDelta += OnManipulationDelta; this.ManipulationCompleted += OnManipulatedCompleted; } void OnManipulatedCompleted(object sender, ManipulationCompletedRoutedEventArgs e) { this.TranslateContent(0); if (IsManipulationSignificant(e.Cumulative.Translation.X)) { SwipeEventArgs args = new SwipeEventArgs() { Direction = e.Cumulative.Translation.X < 0 ? SwipeDirection.Left : SwipeDirection.Right }; this.Swiped?.Invoke(this, args); } } bool IsManipulationSignificant(double x) { bool significant = Math.Abs(x) > (SIGNIFICANT_TRANSLATE_FACTOR * this.contentElement.ActualWidth); return (significant); } void OnManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e) { this.SetVisualStateForManipulation(e.Cumulative.Translation); this.TranslateContent(e.Cumulative.Translation.X); } void SetVisualStateForManipulation(Point p) { var direction = this.DirectionOfManipulation(p); VisualStateManager.GoToState(this, direction.ToString(), true); } void TranslateContent(double x) { if (this.contentElement != null) { // This may well break if there's already a transform on this element :-( TranslateTransform transform = (this.contentElement.RenderTransform as TranslateTransform); if (transform == null) { transform = new TranslateTransform(); this.contentElement.RenderTransform = transform; } transform.X = x; } } SwipeDirection DirectionOfManipulation(Point p) { SwipeDirection d = SwipeDirection.Default; if (p.X != 0) { d = p.X < 0 ? SwipeDirection.Left : SwipeDirection.Right; } return (d); } protected override void OnApplyTemplate() { base.OnApplyTemplate(); this.contentElement = this.GetTemplateChild("contentPresenter") as FrameworkElement; } static readonly double SIGNIFICANT_TRANSLATE_FACTOR = 0.20d; FrameworkElement contentElement; }
and that needs a template which I put into a file called Themes\generic.xaml in my project (called App9);
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:App9"> <Style TargetType="local:SwipeContentControl"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="local:SwipeContentControl"> <Grid Background="{TemplateBinding Background}"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="leftRightStates"> <VisualState x:Name="Left"> <VisualState.Setters> <Setter Target="leftContentPresenter.(UIElement.Visibility)" Value="Visible" /> </VisualState.Setters> </VisualState> <VisualState x:Name="Right"> <VisualState.Setters> <Setter Target="rightContentPresenter.(UIElement.Visibility)" Value="Visible" /> </VisualState.Setters> </VisualState> <VisualState x:Name="Default" /> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <ContentPresenter Visibility="Collapsed" x:Name="leftContentPresenter" Content="{TemplateBinding LeftContent}" /> <ContentPresenter Visibility="Collapsed" x:Name="rightContentPresenter" Content="{TemplateBinding RightContent}" /> <ContentPresenter x:Name="contentPresenter" Foreground="{TemplateBinding Foreground}" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" /> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
and then using the control in a ListView scenario looks something like this;
<ListView> <ListView.ItemContainerStyle> <Style TargetType="ListViewItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch" /> </Style> </ListView.ItemContainerStyle> <ListView.Items> <x:String>Mail One</x:String> <x:String>Mail Two</x:String> <x:String>Mail Three</x:String> </ListView.Items> <ListView.ItemTemplate> <DataTemplate> <local:SwipeContentControl HorizontalContentAlignment="Stretch" Swiped="OnSwiped"> <local:SwipeContentControl.LeftContent> <Grid Background="LimeGreen"> <TextBlock Text="Flag" Foreground="White" Margin="0,0,4,0" HorizontalAlignment="Right" VerticalAlignment="Center" /> </Grid> </local:SwipeContentControl.LeftContent> <local:SwipeContentControl.RightContent> <Grid Background="Red"> <TextBlock Text="Delete" Margin="4,0,0,0" Foreground="White" HorizontalAlignment="Left" VerticalAlignment="Center" /> </Grid> </local:SwipeContentControl.RightContent> <Grid Background="AliceBlue"> <TextBlock FontSize="18" Margin="20" HorizontalAlignment="Center" Text="{Binding}" /> </Grid> </local:SwipeContentControl> </DataTemplate> </ListView.ItemTemplate> </ListView>
with an event handler for the Swiped event looking like this;
void OnSwiped(object sender, SwipeEventArgs args) { Debug.WriteLine($"We were swiped {args.Direction}"); }
As I said at the start - this is far from perfect but it might help a little with what you were wanting to do and you can perhaps tailor it to suit your needs.
I hope it does help - I wrote it up on my blog here where the full code is downloadable.
Thanks
Mike
- Edited by Mike TaultyMicrosoft employee Saturday, February 13, 2016 2:18 PM typo
- Proposed as answer by Amy PengMicrosoft employee Thursday, February 18, 2016 2:44 AM
- Marked as answer by Amy PengMicrosoft employee Monday, February 22, 2016 4:09 AM
Saturday, February 13, 2016 2:17 PM -
Hi,
You might also want to take a look at this control;
https://github.com/FrayxRulez/SwipeListView
which someone later pointed me to.
Thanks,
Mike.
- Marked as answer by Amy PengMicrosoft employee Monday, February 22, 2016 4:09 AM
Saturday, February 13, 2016 4:17 PM
All replies
-
Hello,
Welcome to the Developing Universal Windows apps forum!
As a friendly reminder please make sure to add the appropriate tags to the title of your post as per Guide to posting: subject line tags
If I do not misunderstand you, it seems that you want to create a mail style ListView as following:
You can try to change the ListView control style by right-click the ListView control-->"Edit Template"-->"Edit a Copy...", then it will show you the default style of the ListView control:
After that you can add a button in the control template to implement an action (Archive/Flag).
Or you can modify the ListView Datatemplate to add a button to implement an action (Archive/Flag).
If I have misunderstood you, please feel free to let me know.Best Regards,
Amy Peng
We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
Click HERE to participate the survey.- Edited by Amy PengMicrosoft employee Thursday, February 11, 2016 2:27 PM
Thursday, February 11, 2016 2:27 PM -
Hi,
Really interesting question. I don't know that I have the full answer for you but maybe I can help a little.
I wondered whether the best way to do this was to create a custom ListView or a custom ListViewItem or maybe re-remplate the ListViewItem.
I didn't have a lot of success with those so I knocked up the starting point for a control which displays 3 pieces of content;
- the main content
- content to be displayed as the main content swipes left
- content to be displayed as the main content swipes right
and the control raises a Swiped event (with the direction) when it detects a swipe of a 'significant' size.
The code for my control ended up looking like this (I didn't spend a huge amount of time on it - it's just a starting point);
using System; using Windows.Foundation; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Markup; using Windows.UI.Xaml.Media; // these names line up with visual states in generic.xaml so // rename at your peril. enum SwipeDirection { Left, Right, Default }; class SwipeEventArgs : EventArgs { public SwipeDirection Direction { get; set; } } [ContentProperty(Name = "Content")] class SwipeContentControl : Control { public event EventHandler<SwipeEventArgs> Swiped; public static DependencyProperty LeftContentProperty = DependencyProperty.Register( "LeftContent", typeof(object), typeof(SwipeContentControl), null); public static DependencyProperty RightContentProperty = DependencyProperty.Register( "RightContent", typeof(object), typeof(SwipeContentControl), null); public static DependencyProperty ContentProperty = DependencyProperty.Register( "Content", typeof(object), typeof(SwipeContentControl), null); public static DependencyProperty LeftContentTemplateProperty = DependencyProperty.Register( "LeftContentTemplate", typeof(DataTemplate), typeof(SwipeContentControl), null); public static DependencyProperty RightContentTemplateProperty = DependencyProperty.Register( "RightContentTemplate", typeof(DataTemplate), typeof(SwipeContentControl), null); public static DependencyProperty ContentTemplateProperty = DependencyProperty.Register( "ContentTemplate", typeof(DataTemplate), typeof(SwipeContentControl), null); public object LeftContent { get { return (base.GetValue(LeftContentProperty)); } set { base.SetValue(LeftContentProperty, value); } } public DataTemplate LeftContentTemplate { get { return ((DataTemplate)base.GetValue(LeftContentTemplateProperty)); } set { base.SetValue(LeftContentTemplateProperty, value); } } public DataTemplate RightContentTemplate { get { return ((DataTemplate)base.GetValue(RightContentTemplateProperty)); } set { base.SetValue(RightContentTemplateProperty, value); } } public DataTemplate ContentTemplate { get { return ((DataTemplate)base.GetValue(ContentTemplateProperty)); } set { base.SetValue(ContentTemplateProperty, value); } } public object RightContent { get { return (base.GetValue(RightContentProperty)); } set { base.SetValue(RightContentProperty, value); } } public object Content { get { return (base.GetValue(ContentProperty)); } set { base.SetValue(ContentProperty, value); } } public SwipeContentControl() { this.DefaultStyleKey = typeof(SwipeContentControl); this.ManipulationMode = ManipulationModes.TranslateX; this.ManipulationDelta += OnManipulationDelta; this.ManipulationCompleted += OnManipulatedCompleted; } void OnManipulatedCompleted(object sender, ManipulationCompletedRoutedEventArgs e) { this.TranslateContent(0); if (IsManipulationSignificant(e.Cumulative.Translation.X)) { SwipeEventArgs args = new SwipeEventArgs() { Direction = e.Cumulative.Translation.X < 0 ? SwipeDirection.Left : SwipeDirection.Right }; this.Swiped?.Invoke(this, args); } } bool IsManipulationSignificant(double x) { bool significant = Math.Abs(x) > (SIGNIFICANT_TRANSLATE_FACTOR * this.contentElement.ActualWidth); return (significant); } void OnManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e) { this.SetVisualStateForManipulation(e.Cumulative.Translation); this.TranslateContent(e.Cumulative.Translation.X); } void SetVisualStateForManipulation(Point p) { var direction = this.DirectionOfManipulation(p); VisualStateManager.GoToState(this, direction.ToString(), true); } void TranslateContent(double x) { if (this.contentElement != null) { // This may well break if there's already a transform on this element :-( TranslateTransform transform = (this.contentElement.RenderTransform as TranslateTransform); if (transform == null) { transform = new TranslateTransform(); this.contentElement.RenderTransform = transform; } transform.X = x; } } SwipeDirection DirectionOfManipulation(Point p) { SwipeDirection d = SwipeDirection.Default; if (p.X != 0) { d = p.X < 0 ? SwipeDirection.Left : SwipeDirection.Right; } return (d); } protected override void OnApplyTemplate() { base.OnApplyTemplate(); this.contentElement = this.GetTemplateChild("contentPresenter") as FrameworkElement; } static readonly double SIGNIFICANT_TRANSLATE_FACTOR = 0.20d; FrameworkElement contentElement; }
and that needs a template which I put into a file called Themes\generic.xaml in my project (called App9);
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:App9"> <Style TargetType="local:SwipeContentControl"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="local:SwipeContentControl"> <Grid Background="{TemplateBinding Background}"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="leftRightStates"> <VisualState x:Name="Left"> <VisualState.Setters> <Setter Target="leftContentPresenter.(UIElement.Visibility)" Value="Visible" /> </VisualState.Setters> </VisualState> <VisualState x:Name="Right"> <VisualState.Setters> <Setter Target="rightContentPresenter.(UIElement.Visibility)" Value="Visible" /> </VisualState.Setters> </VisualState> <VisualState x:Name="Default" /> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <ContentPresenter Visibility="Collapsed" x:Name="leftContentPresenter" Content="{TemplateBinding LeftContent}" /> <ContentPresenter Visibility="Collapsed" x:Name="rightContentPresenter" Content="{TemplateBinding RightContent}" /> <ContentPresenter x:Name="contentPresenter" Foreground="{TemplateBinding Foreground}" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" /> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
and then using the control in a ListView scenario looks something like this;
<ListView> <ListView.ItemContainerStyle> <Style TargetType="ListViewItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch" /> </Style> </ListView.ItemContainerStyle> <ListView.Items> <x:String>Mail One</x:String> <x:String>Mail Two</x:String> <x:String>Mail Three</x:String> </ListView.Items> <ListView.ItemTemplate> <DataTemplate> <local:SwipeContentControl HorizontalContentAlignment="Stretch" Swiped="OnSwiped"> <local:SwipeContentControl.LeftContent> <Grid Background="LimeGreen"> <TextBlock Text="Flag" Foreground="White" Margin="0,0,4,0" HorizontalAlignment="Right" VerticalAlignment="Center" /> </Grid> </local:SwipeContentControl.LeftContent> <local:SwipeContentControl.RightContent> <Grid Background="Red"> <TextBlock Text="Delete" Margin="4,0,0,0" Foreground="White" HorizontalAlignment="Left" VerticalAlignment="Center" /> </Grid> </local:SwipeContentControl.RightContent> <Grid Background="AliceBlue"> <TextBlock FontSize="18" Margin="20" HorizontalAlignment="Center" Text="{Binding}" /> </Grid> </local:SwipeContentControl> </DataTemplate> </ListView.ItemTemplate> </ListView>
with an event handler for the Swiped event looking like this;
void OnSwiped(object sender, SwipeEventArgs args) { Debug.WriteLine($"We were swiped {args.Direction}"); }
As I said at the start - this is far from perfect but it might help a little with what you were wanting to do and you can perhaps tailor it to suit your needs.
I hope it does help - I wrote it up on my blog here where the full code is downloadable.
Thanks
Mike
- Edited by Mike TaultyMicrosoft employee Saturday, February 13, 2016 2:18 PM typo
- Proposed as answer by Amy PengMicrosoft employee Thursday, February 18, 2016 2:44 AM
- Marked as answer by Amy PengMicrosoft employee Monday, February 22, 2016 4:09 AM
Saturday, February 13, 2016 2:17 PM -
Hi,
You might also want to take a look at this control;
https://github.com/FrayxRulez/SwipeListView
which someone later pointed me to.
Thanks,
Mike.
- Marked as answer by Amy PengMicrosoft employee Monday, February 22, 2016 4:09 AM
Saturday, February 13, 2016 4:17 PM