XAML Slider inside ScrollViewer - xaml

For Windows 10 UWP app I have such XAML structure:
<ScrollViewer>
<StackPanel>
<Slider />
<Slider />
...
</StackPanel>
</ScrollViewer>
I would like to create such user experience:
When the user begins horizontal swipe gesture, the slider under the touch should receive the input and start changing its value, while vertical scrolling is completely disabled (even when the user continue draw circle motions)
When the user begins vertical swipe gesture, the scrollviewer should receive the input and start scrolling vertically, while sliders under the touch should stay intact (even when the user continue draw circle motions).
Is it possible to implement this behavior in pure XAML? I think I have tried all possible combinations of properties related to scroll... No luck. Any idea anybody?

After testing on mobile emulator with OS version 10586, I found that when the ScrollViewer is vertically scrolled, it won't effect the Slider inside even if I draw circle, and when the Slider is swiped horizontally, only when its value reaches the max value, the vertical scrolling of the ScrollViewer will be effected if I draw circle.
Is it possible to implement this behavior in pure XAML?
Yes, it is possible.
When the user begins horizontal swipe gesture, the slider under the touch should receive the input and start changing its value, while vertical scrolling is completely disabled (even when the user continue draw circle motions)
You can install the Microsoft.Xaml.Behaviors.Uwp.Managed package in your project, then use its DataTriggerBehavior for example like this:
<ScrollViewer x:Name="scrollViewer" HorizontalScrollMode="Disabled" VerticalScrollMode="Auto">
<Interactivity:Interaction.Behaviors>
<Core:DataTriggerBehavior Binding="{Binding ElementName=slider1,Path=FocusState}" ComparisonCondition="NotEqual" Value="Unfocused">
<Core:ChangePropertyAction PropertyName="VerticalScrollMode" Value="Disabled" />
</Core:DataTriggerBehavior>
<Core:DataTriggerBehavior Binding="{Binding ElementName=slider1,Path=FocusState}" ComparisonCondition="Equal" Value="Unfocused">
<Core:ChangePropertyAction PropertyName="VerticalScrollMode" Value="Auto" />
</Core:DataTriggerBehavior>
<Core:DataTriggerBehavior Binding="{Binding ElementName=slider2,Path=FocusState}" ComparisonCondition="NotEqual" Value="Unfocused">
<Core:ChangePropertyAction PropertyName="VerticalScrollMode" Value="Disabled" />
</Core:DataTriggerBehavior>
<Core:DataTriggerBehavior Binding="{Binding ElementName=slider2,Path=FocusState}" ComparisonCondition="Equal" Value="Unfocused">
<Core:ChangePropertyAction PropertyName="VerticalScrollMode" Value="Auto" />
</Core:DataTriggerBehavior>
</Interactivity:Interaction.Behaviors>
<StackPanel Height="1300">
<Slider Margin="0,200" x:Name="slider1" />
<Slider x:Name="slider2" />
</StackPanel>
</ScrollViewer>
When using this package in xaml, you will need to declare it in the header for example like this:
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
xmlns:Media="using:Microsoft.Xaml.Interactions.Media"
As you can see in my code, I compared FocusState property of the Slider, if its value is Unfocused, then the vertical scroll mode of the ScrollViewer is enabled. So when user interact with this layout, after swiping on the Slider, he will need to click on the blank part to make the Slider lost focus first, then the vertical scroll mode can be enabled.
When the user begins vertical swipe gesture, the scrollviewer should receive the input and start scrolling vertically, while sliders under the touch should stay intact (even when the user continue draw circle motions).
Base on my test, I think this gesture is ensured by default, so I didn't code for this. If it is not by your side, please leave a comment and provide your device type and OS version so can I have a test.

I have a very similar issue and was able to resolve it with the following custom control. This is a CommandSlider that also allows you to send commands like a button after the sliding is complete (not what you need) but the ScrollViewer manipulation code is in there also. See if this helps your situation. It allows you to do all the work in complete XAML like you've requested.
NOTE: The slider must have ManipulationMode="TranslateX" or "TranslateY"depending on the orientation for this to work also.
public sealed class CommandSlider : Slider
{
public CommandSlider()
{
IsThumbToolTipEnabled = false;
PointerCaptureLost += (s, e) => (Command?.CanExecute(CommandParameter)).GetValueOrDefault().Switch(() => Command?.Execute(CommandParameter));
Loaded += (s, e) => ParentScrollViewer = this.GetParent<ScrollViewer>();
}
private ScrollViewer ParentScrollViewer { get; set; }
protected override void OnManipulationDelta(ManipulationDeltaRoutedEventArgs e)
{
base.OnManipulationDelta(e);
if (ParentScrollViewer != null)
{
var scrollX = Orientation == Orientation.Vertical
? e.Position.X * -1 + ParentScrollViewer.HorizontalOffset
: ParentScrollViewer.HorizontalOffset;
var scrollY = Orientation == Orientation.Horizontal
? e.Position.Y * -1 + ParentScrollViewer.VerticalOffset
: ParentScrollViewer.VerticalOffset;
var zoom = ParentScrollViewer.ZoomFactor;
ParentScrollViewer.ChangeView(scrollX, scrollY, zoom);
}
}
public object CommandParameter
{
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register(nameof(CommandParameter), typeof(object), typeof(CommandSlider), new PropertyMetadata(null));
public ICommand Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(nameof(Command), typeof(ICommand), typeof(CommandSlider), new PropertyMetadata(null));
public double SourceValue
{
get => (double)GetValue(SourceValueProperty);
set => SetValue(SourceValueProperty, value);
}
public static readonly DependencyProperty SourceValueProperty =
DependencyProperty.Register(nameof(SourceValue), typeof(double), typeof(CommandSlider), new PropertyMetadata(0d,
(s, e) => (s as CommandSlider).Value = (double)e.NewValue));
}
Custom Extension Method
public static class XAMLExtensions
{
public static T GetParent<T>(this DependencyObject dependencyObject) where T : DependencyObject
{
var parentDependencyObject = VisualTreeHelper.GetParent(dependencyObject);
switch (parentDependencyObject)
{
case null:
return null;
case T parent:
return parent;
default:
return GetParent<T>(parentDependencyObject);
}
}
}

Related

custom entry box with icon for xamarin form (android,ios)

Entry box should be rounded with an icon to the left or right in it. I'm using the code presented here to create this custom entry.
1. Remove the rectangular border of Entry
Used CustomRender to achieve this.
Forms
public class NoUnderlineEntry : Entry
{
public NoUnderlineEntry()
{
}
}
Android
Set Background to null
public class NoUnderLineEntryRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
this.Control.Background = null;
}
}
iOS
Set BorderStyle to None
public class NoUnderlineEntryRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
this.Control.BorderStyle = UIKit.UITextBorderStyle.None;
}
}
2. Placing Image next to Entry
Adding Image and Entry to the same Grid in two columns.
3. Adding Rounded border to the Entry and Image
Add them inside a Frame with CornerRadius.
XAML
<StackLayout>
<Frame
Padding="10, 5, 10, 5"
HasShadow="False"
BorderColor="Gray"
CornerRadius="30">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<local:NoUnderlineEntry/>
<Image Source="icon.png" Grid.Column="1" WidthRequest="50" Aspect="AspectFit"/>
</Grid>
</Frame>
</StackLayout>
UI result:
Please note: I won't present a copy-and-paste-able answer, but rather an outline on how to add the images. You'll have to integrate the code in your solution by yourself.
On iOS
There already is an answered question on how to achieve this with Swift on iOS, you can find it here.
Basically what to do is to set the right view (or left view respectively) on the UITextField from your custom renderer (in OnElementChanged).
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
var imageView = new UIImageView(new CGRect(0, 0, 20, 20));
var image = UIImage.FromFile("ic_location.png");
imageView.Image = image;
this.Control.RightView = imageView;
this.Control.RightViewMode = UITextFieldViewMode.Always;
}
This sets the view in the right of the UITextField to a UIImageView. If you wanted to show the icon before the text instead, you'd have to set LeftView and LeftViewMode instead. This is how it looks like. (I intentionally did not inline the image, because it rendered the answer less redable.)
Of course the file ic_location.png has to be in your platform projects resources.
You may need some fine tuning, but basically that's it.
On Android
The TextView.setCompoundDrawablesWithIntrinsicBounds
Sets the Drawables (if any) to appear to the left of, above, to the right of, and below the text. Use null if you do not want a Drawable there. The Drawables' bounds will be set to their intrinsic bounds. (source)
By loading the icon from the resource and setting it with SetCompoundDrawablesWithIntrinsicBounds (uppercase now, since we're now on C#) you can display the Entry with the icon:
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
this.Control.SetCompoundDrawablesWithIntrinsicBounds(null, null, this.GetIcon(), null);
this.Control.CompoundDrawablePadding = 25;
}
private Drawable GetIcon()
{
int resID = Resources.GetIdentifier("ic_location", "drawable", this.Context.PackageName);
var drawable = ContextCompat.GetDrawable(this.Context, resID);
var bitmap = ((BitmapDrawable)drawable).Bitmap;
return new BitmapDrawable(Resources, Bitmap.CreateScaledBitmap(bitmap, 20, 20, true));
}
This is how the Android version looks like.
For showing the icon on left left, pass the drawable to the first parameter instead of the third.

UWP Binding: Changing backgrounds in XAML using C#

Suppose I am making a simple UWP application which navigates through several pages. I would like to have a common background for all pages, depending on which background a user has selected from the Settings page.
I have a SettingsPage.xaml with a comboBox (and Grid Background that needs to change):
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ComboBox Name="ColourSelect" SelectionChanged="ComboBox_SelectionChanged">
<ComboBoxItem Name="Red">Red</ComboBoxItem>
<ComboBoxItem Name="Green">Green</ComboBoxItem>
<ComboBoxItem Name="Blue">Blue</ComboBoxItem>
</ComboBox>
</Grid>
Which interfaces with my SettingsPage.xaml.cs file:
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Change background
if (Red.IsSelected) { } // Change to Red.png
else if (Green.IsSelected) { } // Change to Green.png
else if (Blue.IsSelected) { } // Change to Blue.png
}
I have set up my App.xaml to contain a background resource, but I'm not sure how to bind it to the C# in Settings.xaml.cs.
<Application.Resources>
<Style TargetType="Grid" x:Key="CommonBackground">
<Setter Property="Background" Value="{ <!-- Some image. How to bind? --> }"
</Style>
</Application.Resources>
What should I return to bind the user decision to the Application resources?
Thank you in advance!
This requires few changes in different pieces of application. Follow my steps.
In this case I a am creating two Resources. One that will maintain the Settings Combobox Colour Scheme. Second one is BitMapImage in Resource.
So my Application.Resource will look something like below.
<Application.Resources>
<image:BitmapImage x:Key="BackgroundSource" UriSource="ms-appx:///Assets/Red.png" />
<x:String x:Key="BackgroundBrush">Red</x:String>
</Application.Resources>
Make sure you are adding xmlns:image="using:Windows.UI.Xaml.Media.Imaging" in your App.xaml.
Now Create a Static Method inside App.xaml.cs that will be used to update Background to the Page during Run time. It should be something like below.
public static void UpdateBGColors(string Color)
{
switch (Color)
{
case "Red":
Current.Resources["BackgroundSource"] = "ms-appx:///Assets/Red.png";
break;
case "Green":
Current.Resources["BackgroundSource"] = "ms-appx:///Assets/Green.png";
break;
case "Blue":
Current.Resources["BackgroundSource"] = "ms-appx:///Assets/Blue.png";
break;
default:
Current.Resources["BackgroundSource"] = "ms-appx:///Assets/Red.png";
break;
}
}
Now Your combobox_SelectionChanged should look like below.
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox cb = sender as ComboBox;
ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
localSettings.Values["BackgroundBrush"] = (cb.SelectedValue as ComboBoxItem).Content;
App.UpdateBGColors((cb.SelectedValue as ComboBoxItem).Content.ToString());
}
Now you need to wire up the Background of each page to the Resource BackgroundSource. So anywhere you want the background to be set based on settings add below lines of code
<Grid>
<Grid.Background>
<ImageBrush ImageSource="{StaticResource BackgroundSource}" />
</Grid.Background>
......
</Grid>
At this point, if you change the setting in setting page and if you navigate back to original page that you came into setting page, The background should be set automatically to whatever you selected in Settings.
But you also want to make sure the same background is loaded when the app is opened next time. To do that in App.xaml.cs, Add below lines in the beginning of OnLaunched Event.
ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
if (localSettings.Values["BackgroundBrush"] != null)
{
UpdateBGColors(localSettings.Values["BackgroundBrush"].ToString());
}
Since in settings page, you are saving BackgroundBrush Everytime you change the Combobox Item, Whenever your app is loading, Based on the BackgroundBrush BackgroundSource will be assigned to correct Uri and will be used as Page Backhground.
Full Repo is available Here
Good Luck.
[Update] You can use this, and after save your settings.
SettingsPage.xaml
<Grid>
<Grid.Background>
<ImageBrush x:Name="colorImage" Stretch="UniformToFill"/>
</Grid.Background>
<ComboBox Name="ColourSelect" SelectionChanged="ComboBox_SelectionChanged">
<ComboBoxItem Name="Red">Red</ComboBoxItem>
<ComboBoxItem Name="Green">Green</ComboBoxItem>
<ComboBoxItem Name="Blue">Blue</ComboBoxItem>
</ComboBox>
</Grid>
SettingsPage.xaml.cs
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (Red.IsSelected)
{
ChangeColorImage("ms-appx:///Assets/Red.png");
}
else if (Green.IsSelected)
{
ChangeColorImage("ms-appx:///Assets/Green.png");
}
else if (Blue.IsSelected)
{
ChangeColorImage("ms-appx:///Assets/Blue.png");
}
}
private void ChangeColorImage(string imageUrl)
{
// using Windows.UI.Xaml.Media.Imaging;
BitmapImage imageSource = new BitmapImage(new Uri(imageUrl));
colorImage.ImageSource = imageSource;
}

How to make sure a Popup control match its parent Page when the parent is resized? UWP

I have a Popup which will fill the whole page when opened.
<Grid x:Name="gridRoot" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Button Content="Open" HorizontalAlignment="Center" Click="{x:Bind viewModel.OpenPopup}" />
<Popup x:Name="popupCorrect" VerticalAlignment="Top" IsOpen="{Binding IsOpen}" IsLightDismissEnabled="False">
<Popup.ChildTransitions>
<TransitionCollection>
<PaneThemeTransition Edge="Left" />
</TransitionCollection>
</Popup.ChildTransitions>
<uc:MyPopup Width="{Binding ElementName=gridRoot, Path=ActualWidth}" Height="{Binding ElementName=gridRoot, Path=ActualHeight}"/>
</Popup>
</Grid>
The Popup is a UserControl
<Grid Background="Red">
<Button Content="Close" HorizontalAlignment="Center" Click="{x:Bind viewModel.ClosePopup}" />
</Grid>
The page
When popup is shown
Close the popup, resize the page, then reopen the popup. Notice that it does not match the new size of container page even though its Width and Height is bound to gridRoot . Do I have to manually set a new Width and Height for the popup? Why can't I achieve this with binding? This issue also appears on mobile during 'OrientationChanged'
Based on Decade Moon comment, this is how to resize the popup to match the parent container as its size changed.
Create a dependency property in the code behind
public double PageWidth
{
get { return (double)GetValue(PageWidthProperty); }
set { SetValue(PageWidthProperty, value); }
}
public static readonly DependencyProperty PageWidthProperty =
DependencyProperty.Register("PageWidth", typeof(double), typeof(GamePage), new PropertyMetadata(0d));
public double PageHeight
{
get { return (double)GetValue(PageHeightProperty); }
set { SetValue(PageHeightProperty, value); }
}
public static readonly DependencyProperty PageHeightProperty =
DependencyProperty.Register("PageHeight", typeof(double), typeof(GamePage), new PropertyMetadata(0d));
Update the value on SizeChanged event
private void GamePage_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (e.NewSize.Width > 0d && e.NewSize.Height > 0d)
{
PageWidth = e.NewSize.Width;
PageHeight = e.NewSize.Height;
}
}
Then in XAML, just use x:Bind to bind the popup width and height
<Popup x:Name="popupCorrect" VerticalAlignment="Top" IsOpen="{Binding IsPopupCorrectOpen, Mode=TwoWay}" IsLightDismissEnabled="False">
<Popup.ChildTransitions>
<TransitionCollection>
<PaneThemeTransition Edge="Left" />
</TransitionCollection>
</Popup.ChildTransitions>
<uc:PopupCorrect Width="{x:Bind PageWidth, Mode=TwoWay}" Height="{x:Bind PageHeight, Mode=TwoWay}"/>
</Popup>
Pretty straight forward. Just remember not to use the ActualWidth or ActualHeight properties for binding source as they do not raise the PropertyChanged event.
Although it has an ActualWidthProperty backing field, ActualWidth does not raise property change notifications and it should be thought of as a regular CLR property and not a dependency property.
For purposes of ElementName binding, ActualWidth does not post updates when it changes (due to its asynchronous and run-time calculated nature). Do not attempt to use ActualWidth as a binding source for an ElementName binding. If you have a scenario that requires updates based on ActualWidth, use a SizeChanged handler.
#PutraKg have a great way.
But I have two way to solve it.
The first is set the VerticalAlignment="Center" HorizontalAlignment="Center" that can make the popup in the center.
But I think youare not content to put it in the center.
The great way is use the screen position.
You can get the Grid's screen postion and make it to popup.
In open button
private void Button_OnClick(object sender, RoutedEventArgs e)
{
var grid = (UIElement)popupCorrect.Parent; //get grid
var p = grid.TransformToVisual (Window.Current.Content).TransformPoint(new Point(0, 0)); //get point
popupCorrect.HorizontalOffset = p.X;
popupCorrect.VerticalOffset = p.Y;
popupCorrect.IsOpen = !popupCorrect.IsOpen;
}

UWP FlipView prevent user interactions

Yes, i have googled for it, this solution doesn't do the trick:
Disable navigation on FlipView
Because i want to remain changing items with animations, but only programmatically.
I have investigated the FlipView template and found that all interactions/animations etc. are built using the ScrollingHost by name:
<ScrollViewer x:Name="ScrollingHost" AutomationProperties.AccessibilityView="Raw" BringIntoViewOnFocusChange="{TemplateBinding ScrollViewer.BringIntoViewOnFocusChange}" HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}" HorizontalSnapPointsType="MandatorySingle" HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}" IsTabStop="False" IsHorizontalRailEnabled="{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}" IsHorizontalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsHorizontalScrollChainingEnabled}" IsVerticalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsVerticalScrollChainingEnabled}" IsVerticalRailEnabled="{TemplateBinding ScrollViewer.IsVerticalRailEnabled}" IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}" Padding="{TemplateBinding Padding}" TabNavigation="{TemplateBinding TabNavigation}" VerticalSnapPointsType="MandatorySingle" VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}" VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}" ZoomMode="Disabled">
<ItemsPresenter/>
</ScrollViewer>
So, i have created a custom class MyFlipView and derivered from FlipView, and overrided MouseWheel ( this gives me the needed behavior on desktop ), and deleted navigation buttons from template ( this also limits user interactions ). But the only thing that remains, user is still able to drag the items by pointer ( tablets, phones, maybe even PC with touch screens ). here is my code:
public class MyFlipView : FlipView
{
ScrollViewer scroll;
public MyFlipView()
{
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
scroll = GetTemplateChild("ScrollingHost") as ScrollViewer;
scroll.HorizontalScrollMode = ScrollMode.Disabled;
scroll.VerticalScrollMode = ScrollMode.Disabled;
scroll.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
scroll.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;
scroll.IsHorizontalRailEnabled = false;
scroll.IsVerticalRailEnabled = false;
scroll.IsVerticalScrollChainingEnabled = false;
scroll.IsHorizontalScrollChainingEnabled = false;
scroll.IsHoldingEnabled = false;
}
protected override void OnPointerWheelChanged(PointerRoutedEventArgs e)
{
e.Handled = true;
//base.OnPointerWheelChanged(e);
}
}
As you can see i tried do disable all the scrolling in the ScrollingHost, but still it is possible to switch items on touchscreens. How can i disable them also?
Set the FlipView's IsHitTestVisible property to false.
Then set KeyboardNavigation.DirectionalNavigation to none
Which is basically saying "You can't touch me or tab to me"
Thomas Schneiter's answer would still allow it to be reached via tabbing / other buggy methods of navigation.
If you don't need any interaction with the FlipView, an easy but... well kinda ugly solution is to put a (almost) transparent rectangle on top of your FlipView.
<Grid>
<FlipView ... />
<Rectangle Fill="White" Opacity="0.01" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</Grid>
Over a year later and still no good solution for disabling FlipView's. Here's what I ended up doing which seems to do the trick without having to create a new FlipView control.
Edit the ControlTemplate and remove all the navigation buttons. Add a transparent background to you DataTemplate container. Add a PointerWheelChanged event handler to the DataTemplate container and have it set e.Handled = true;. Finally, add a SelectionChanged event handler to the FlipView and have it reset the selected item back to the original.
XAML
<FlipView x:Name="MyFlipView" ItemsSource="{x:Bind Items,Mode=OneWay}" SelectionChanged="FlipView_SelectionChanged">
<FlipView.ItemTemplate>
<DataTemplate>
<Grid Background="Transparent" PointerWheelChanged="Item_PointerWheelChanged">
<!--You Item XAML-->
</Grid>
</DataTemplate>
</FlipView.ItemTemplate>
<FlipView.Template>
<ControlTemplate TargetType="FlipView">
<Grid>
<!--Default ScrollViewer settings omitted, but no changes needed-->
<ScrollViewer>
<ItemsPresenter />
</ScrollViewer>
</Grid>
</ControlTemplate>
</FlipView.Template>
</FlipView>
Code Behind
private bool _isDisabled;
private object _selectedItem;
public ObservableCollection<object> Items { get; } = new ObservableCollection<object>();
//methods for setting Items, _isDisabled, and _selectedItem omitted
private void Item_PointerWheelChanged(object sender, PointerRoutedEventArgs e)
{
//this prevents the wheel from changing the items
e.Handled = _isDisabled;
}
private void FlipView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//Can't find a way to disable keyboard from changing items so we'll monitor the change and flip it back
if (_isDisabled)
{
this.SetupSelectedItem();
}
}
private void SetupSelectedItem()
{
//do a check here to prevent SelectionChanged events from firing.
if (this.MyFlipView.SelectedItem != _selectedItem)
{
this.MyFlipView.SelectedItem = _selectedItem;
}
}
UPDATE
Ok, TouchScreen was still having some problems so I took another stab at it. There are two levels we need to work on, the FlipViewItem and the FlipView. Unfortunately we have to sub-class these because there aren't any overrides we can do to interrupt the default behavior. I also wanted to use binding to be able to turn the flipping on or off. This only supports Horizontal orientation but can easily be adjusted to support Vertical.
Custom FlipViewItem is needed to control the mouse and keyboard.
public sealed class GalleryFlipViewItem : FlipViewItem
{
public bool IsFlipEnabled { get; set; }
protected override void OnKeyDown(KeyRoutedEventArgs e)
{
e.Handled = !this.IsFlipEnabled;
base.OnKeyDown(e);
}
protected override void OnPointerWheelChanged(PointerRoutedEventArgs e)
{
e.Handled = !this.IsFlipEnabled;
base.OnPointerWheelChanged(e);
}
}
Custom FlipView is needed to control the touch and trickle the setting to the custom FlipViewItem.
public sealed class GalleryFlipView : FlipView
{
public static readonly DependencyProperty IsFlipEnabledProperty =
DependencyProperty.Register("IsFlipEnabled", typeof(bool), typeof(GalleryFlipView), new PropertyMetadata(true, IsFlipEnabledChanged));
private static void IsFlipEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is GalleryFlipView control)
{
if (control.GetTemplateChild("ScrollingHost") is ScrollViewer scrollViewer)
{
scrollViewer.HorizontalScrollMode = control.IsFlipEnabled ? ScrollMode.Auto : ScrollMode.Disabled;
}
if (control.ContainerFromItem(control.SelectedItem) is GalleryFlipViewItem flipViewItem)
{
flipViewItem.IsFlipEnabled = control.IsFlipEnabled;
}
}
}
public bool IsFlipEnabled
{
get { return (bool)GetValue(IsFlipEnabledProperty); }
set { SetValue(IsFlipEnabledProperty, value); }
}
protected override DependencyObject GetContainerForItemOverride()
{
return new GalleryFlipViewItem
{
IsFlipEnabled = this.IsFlipEnabled
};
}
}
Now we need to add the styles for each of these controls. I just copied the default styles for FlipViewItem and FlipView. I made a small adjustment to the FlipView by removing the up/down buttons and added binding to control the left/right button's visibility. To do this, wrap the original button in a grid and then use a converter to convert the bool to a visibility enum.
<Grid HorizontalAlignment="Left" VerticalAlignment="Center" Visibility="{Binding IsFlipEnabled,RelativeSource={RelativeSource TemplatedParent},Converter={StaticResource BooleanToVisibleConverter}}">
<Button x:Name="PreviousButtonHorizontal" Height="36" IsTabStop="False" Template="{StaticResource HorizontalPreviousTemplate}" UseSystemFocusVisuals="False" Width="20" />
</Grid>
<Grid HorizontalAlignment="Right" VerticalAlignment="Center" Visibility="{Binding IsFlipEnabled,RelativeSource={RelativeSource TemplatedParent},Converter={StaticResource BooleanToVisibleConverter}}">
<Button x:Name="NextButtonHorizontal" Height="36" IsTabStop="False" Template="{StaticResource HorizontalNextTemplate}" UseSystemFocusVisuals="False" Width="20" />
</Grid>
Since IsFlipEnabled is a DependencyProperty you can use binding, xaml, or code behind to control it.
<controls:GalleryFlipView IsFlipEnabled="False">
Finally, this seems like the right solution. So frustrating we have to do so much work to simply turn this feature on and off.

Binding the ZIndex of items in an ItemsControl

Using Caliburn.Micro for a WinRT application, I would like to control the ZIndex of items displays in an ItemsControl.
When a user taps on an item, it should become the topmost element.
<ItemsControl Background="White" Height="auto" Width="auto" x:Name="Parts"
HorizontalAlignment="Left"
VerticalAlignment="Top"
>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas></Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
The viewmodel bound to the view above contains a property Parts:
private BindableCollection<IPartViewModel> _parts = new BindableCollection<IPartViewModel>();
public BindableCollection<IPartViewModel> Parts
{
get { return _parts; }
set { _parts = value; NotifyOfPropertyChange(() => Parts); }
}
IPartViewModel has different implementations, each with their own View (= custom user controls).
Every implementation of IPartViewModel has a ZIndex property, ready to be bound.
All other bindings (labels, the tapped event, ...) work perfectly, but I cannot figure out what the binding should look like to control the ZIndex.
Many other questions on SO deal with this issue, but none for WinRT.
I ended up achieving this with a custom ItemsControl and overriding the method GetContainerForItemOverride where I return a ContentPresenter with a binding added to the ZIndex property
protected override DependencyObject GetContainerForItemOverride()
{
var cp = new ContentPresenter();
cp.SetBinding(Canvas.ZIndexProperty, new Binding { Path = new PropertyPath("ZIndex") });
return cp;
}