Multi-touch ScrollViewer eating touch events - wpf

WPF 4.0:
I have scroll viewer with many Sliders inside of it. I want the scroll viewer to pan with touch, and I want the internal slider to also respond to touch.
Unfortunately, the scroll viewer is eating the "TouchMove" events and not passing them down to the slider control. Any idea how to fix this?
Here is my XAML:
<Window x:Class="ScrollingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer VerticalScrollBarVisibility="Auto" PanningMode="Both" >
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Height="100" BorderThickness="2" BorderBrush="Black">
<Slider Value="{Binding ., Mode=TwoWay}" Width="300" Minimum="0" Maximum="100" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
And my Code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = Items;
}
public IEnumerable<int> Items
{
get
{
return Enumerable.Range(0, 50);
}
}
}

Please see my answer to this question: ScrollViewer in a touch interface not working properly
I just resolved this issue in our app as well using a custom Thumb control - in my answer I explain what is causing the problem.

This sounds like a case of "routed event marked as handled". Can you try using AddHandler to subscribe to that event, and set the last parameter "handledEventsToo" to true?
Cheers,
Laurent

It's handling the TouchMove event, most likely. There are bubbling events (PreviewTouchMove, etc) you could handle in your Slider control. You would need to coordinate how you want the touch events to be handled.

You can try to make your custom class, derived from ScrollViewer and override OnTouchMove method.
public class CustomScrollViewer : System.Windows.Controls.ScrollViewer
{
protected override void OnTouchMove(System.Windows.Input.TouchEventArgs e)
{
// delete the base.OnTouchMove() call to prevent event being "eat" :)
}
}
Then, you edit the xaml like this:
<Window x:Class="ScrollingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.Template>
<ControlTemplate>
<local:CustomScrollViewer VerticalScrollBarVisibility="Auto" PanningMode="Both" >
<ItemsPresenter />
</local:CustomScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Height="100" BorderThickness="2" BorderBrush="Black">
<Slider Value="{Binding ., Mode=TwoWay}" Width="300" Minimum="0" Maximum="100" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>

Related

Capturing name of StackPanel from Drop event

Within WPF I have the following XAML code:
<Page x:Class="com.MyCo.MyProj.Pages.Configuration.ManageLinkage.MasterLinkage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:com.MyCo.MyProj.Pages.Configuration.ManageLinkage"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Title="MasterLinkage">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TabControl TabStripPlacement="Top" Background="Transparent">
<TabItem Header="Import">
<ListBox Margin="0,5,0,0" Name="lbxImportItems" HorizontalAlignment="Left" VerticalAlignment="Top" Width="110" Background="Transparent"
PreviewMouseLeftButtonDown="lbxImportItems_PreviewMouseLeftButtonDown" >
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" Name="DBImport">
<Image Source="/Images/DBImport25px.png" VerticalAlignment="Center" HorizontalAlignment="Center"></Image>
<TextBlock Text="Database" Foreground="AntiqueWhite"/>
</StackPanel>
<StackPanel Orientation="Vertical" Name="CSVImport">
<Image Source="/Images/CSVImport25px.png" VerticalAlignment="Center" HorizontalAlignment="Center"></Image>
<TextBlock Text="CSV Import" Foreground="AntiqueWhite"/>
</StackPanel>
</ListBox>
</TabItem>
</TabControl>
<Canvas x:Name="cnvsLinkScreen" AllowDrop="True" Grid.Column="1" Background="Transparent" Drop="cnvsLinkScreen_Drop" DragOver="cnvsLinkScreen_DragOver" ></Canvas>
</Grid>
The code for capturing the event is here:
private void cnvsLinkScreen_Drop(object sender, DragEventArgs e)
{
Canvas parent = (Canvas)sender;
object data = e.Data.GetData(typeof(string));
StackPanel objIn = (StackPanel)e.Data;
...
}
The drag and drop work great, the event method created the image in the canvas. However, I want to capture the Name="" from the StackPanels which are dropped.
I found the Name buried super deep in the "DragEventArgs e" object. I was think that there should be a way to cast the object (or the object within that object) as a StackPanel to easily work with it. The above code does not convert the StackPanel object( it's not at the root or the child object; I tried both) so it exceptions on "StackPanel objIn = (StackPanel)e.data;"
How do I either translate the incoming object to a StackPanel or how do I access the Name attribute from the Stackpanel?
I got it. I was close with the translation. To translate / typecast the object to what you are working with I needed to use the following line:
StackPanel objIn = (StackPanel)(e.Data.GetData(typeof(StackPanel)));
Which is slightly different than above.

How To Make A WPF UserControl Act As A Container

I'm trying to create a Toolbar control that can group selected buttons with a border and a label. If there is already a built-in control that will do this then I could use that instead of building a UserControl.
If not, then what I'm wanting to build is a UserControl that would allow me to enter one-to-many of my ImageButton UserControls and set a GroupLabel text like below. Can this be done in WPF?
<User_Controls:ToolbarGroup GroupLabel="Entity">
<User_Controls:ImageButton ButtonText="Entity Setup"/>
<User_Controls:ImageButton ButtonText="New Entity"/>
</User_Controls:ToolbarGroup>
PS: I would post an image but this quirky forum won't allow me to post an image.
If i have got you correctly then I think you can achieve this way also, and on mouse eneter and leave event you can do the button click job.
for setting text you can use a grid and a label inside it to set the text, and Image buttons below it.
<UserControl x:Class="ABC.View.Oats"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Image Source="{Binding Image}" Stretch="Fill"/>
</Grid>
</UserControl>
I think what you're looking for is a GroupBox, it has a header property where you can set the label.
Something like this:
<GroupBox Width="300" Height="100">
<GroupBox.Header>
<Label>Text</Label>
</GroupBox.Header>
<StackPanel>
<Button Content="Button"/>
<Button Content="Button"/>
<Button Content="Button"/>
</StackPanel>
</GroupBox>
I would also recommend using the groupbox, it seems to be doing exactly what you want it to do and it looks neat. Here's some examples on how to use them: http://www.dotnetperls.com/groupbox-wpf
On the other hand, if you believe the groupbox is not sufficient, you could create a control that inherits from the groupbox and you could extend it and add whatever you need to it. It would look like this:
public class customGroupBox: GroupBox{
....Add whatever you need here
}
Thanks for the replies. I tried the GroupBox and it's not the layout we want because we want the label underneath the buttons and centered. I never could find a way to add a collection to the UserControl. Maybe I didn't ask the question right by calling it a container. The code below will work, but it's not elegant. I wanted something that would wrap the layout in a UserControl and allow me to add a variable number of buttons to each toolbar group.
<StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="0">
<Border Background="GhostWhite" BorderBrush="Gainsboro" BorderThickness="1">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<User_Controls:ImageButton ButtonText="New 1"/>
<User_Controls:ImageButton ButtonText="New 2"/>
<User_Controls:ImageButton ButtonText="New 3"/>
</StackPanel>
<Label HorizontalAlignment="Center" Content="Group 1"/>
</StackPanel>
</Border>
<Border Background="GhostWhite" BorderBrush="Gainsboro" BorderThickness="1">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<User_Controls:ImageButton ButtonText="New 4"/>
</StackPanel>
<Label HorizontalAlignment="Center" Content="Group 2"/>
</StackPanel>
</Border>
</StackPanel>
One way to accomplish this is with a custom styled ItemsControl.
You can then reuse it and just bind it to different data.
Please forgive me, this is hand-typed...
In your resources...
<Style x:Key="ToolbarGroupItemsControlStyle" TargetType="ItemsControl">
...
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ItemsControl">
<Grid>
... XAML to form your group with a binding to the
... group name
<ItemsPresenter/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="ToolbarGroupItemTemplate">
<Grid>
... XAML and binding for each toolbar group item ...
</Grid>
</DataTemplate>
In your XAML...
<ItemsControl
Style="{DynamicResource ToolbarGroupItemsControlStyle}"
ItemsSource="{Binding ToolbarGroupItems}"
ItemTemplate="{DynamicResource ToolbarGroupItemTemplate"/>
If your resources above are at the application level, then you can place the ItemsControl above on any Window/UserControl you want.
Your ItemsSource will need to be a collection of a custom type you create that has bindings for the button text, etc.
I hope this is helpful.

Transfer Content value of a UserControl to a Control inside it

I need to extend the bing maps control to be more user MVVM friendly (in specific the ZoomLevel and the BoundingRect properties are not Dependency Properties). I am wrapping the control in a custom usercontrol (I also need to add elements to make other map choices e.g. google maps). I need to transfer the Content Value of the UserControl to the BingMapsControl :
<UserControl x:Class="RevOptWebControls.MVVMMapControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:m="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl"
xmlns:mCore="clr-namespace:Microsoft.Maps.MapControl.Core;assembly=Microsoft.Maps.MapControl"
d:DesignHeight="300" d:DesignWidth="400"
x:Name="Root">
<Grid x:Name="LayoutRoot" Background="White">
<m:Map CredentialsProvider="Al_H1LepP6chseYMu31RK76El6k4SUkx2KVrxeqobE3rTXooFPieuEJ6qiuA211I"
CopyrightVisibility="Collapsed"
LogoVisibility="Collapsed"
ScaleVisibility="Visible"
NavigationVisibility="Visible"
x:Name="MyMap">
</m:Map>
<ComboBox x:Name="c_MapTypes" HorizontalAlignment="Right" VerticalAlignment="Top" SelectedIndex="0" Height="30" SelectionChanged="MapTypes_SelectionChanged">
<ComboBoxItem>Google Roads</ComboBoxItem>
<ComboBoxItem>Google Aerial</ComboBoxItem>
<ComboBoxItem>Bing Maps Roads</ComboBoxItem>
<ComboBoxItem>Bing Maps Aerial</ComboBoxItem>
<ComboBoxItem>Open Street Maps</ComboBoxItem>
<ComboBoxItem>Yahoo Street</ComboBoxItem>
<ComboBoxItem>Yahoo Aerial</ComboBoxItem>
<ComboBoxItem>Blank Map</ComboBoxItem>
</ComboBox>
</Grid>
</UserControl>
Update : Figured out how to do it. Shared the control source code as well : http://basaratali.blogspot.com/2010/12/mvvm-version-of-bing-maps-with-google.html
Why not try a Custom Control with custom template. Use
`{TemplateBinding Content}`
to bind Content with Map control.
Example:
<Style TargetType="local:MVVMMapControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MVVMMapControl">
<Grid x:Name="LayoutRoot"
Background="White">
<m:Map CredentialsProvider="Al_H1LepP6chseYMu31RK76El6k4SUkx2KVrxeqobE3rTXooFPieuEJ6qiuA211I"
CopyrightVisibility="Collapsed"
LogoVisibility="Collapsed"
ScaleVisibility="Visible"
NavigationVisibility="Visible"
x:Name="MyMap"
Content="{TemplateBinding ContentControl.Content}"></m:Map>
<ComboBox x:Name="c_MapTypes"
HorizontalAlignment="Right"
VerticalAlignment="Top"
SelectedIndex="0"
Height="30"
SelectionChanged="MapTypes_SelectionChanged">
<ComboBoxItem>Google Roads</ComboBoxItem>
<ComboBoxItem>Google Aerial</ComboBoxItem>
<ComboBoxItem>Bing Maps Roads</ComboBoxItem>
<ComboBoxItem>Bing Maps Aerial</ComboBoxItem>
<ComboBoxItem>Open Street Maps</ComboBoxItem>
<ComboBoxItem>Yahoo Street</ComboBoxItem>
<ComboBoxItem>Yahoo Aerial</ComboBoxItem>
<ComboBoxItem>Blank Map</ComboBoxItem>
</ComboBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Class:
public class MVVMMapControl : ContentControl // Notice this inherits from ContentControl for its Content Property
{
public MVVMMapControl()
{
this.DefaultStyleKey = typeof(MVVMMapControl);
}
}

How to approach for this kind of UI in WPF?

I have two panel , Left Hand site represents the list of options or menu and right hand side will be list of usercontrol assigned to eatch menu items in the left as Listbox or Items control.
The requirement is
eg. If i move the thumb of the scrollbar in the right hand side panel to anyway near the usercontrol2 , the Usercontrol 2 heading in the heading panel should get activated and if iam moving the thumb to the usercontrol1, the usercontrol 1 heading in the heading panel should get activated and so on.
So how to proceed to accomplish these kind of UI.? Any suggestion is greatly appreciated?
The basic idea is to reduce the no of clicks in the Heading Panel. Right hand side is heavily packed with UI elements so user wants to avoid unnecessary click in the heading.
User will not click on the Left side heading panel. While traversing the right hand panel's scrollviewer the heading should automatically get selected to give the user about the control which he is entering or using now.
Following should work:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Border Grid.Column="0">
<ItemsControl>
<!--List on Left : List of Usercontrols-->
</ItemsControl>
</Border>
<Border Grid.Column="1">
<ScrollViewer VerticalScrollBarVisibility="Visible"
HorizontalScrollBarVisibility="Disabled">
<ItemsControl>
<!--List on Right : List of Usercontrols-->
</ItemsControl>
</ScrollViewer>
</Border>
</Grid>
Use Template Selectors to select which UserControl to display in lists.
EDIT-
You could try something like following:
XAML:
<Window x:Class="WpfApplication1.Window4"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window4"
Height="300"
Width="300">
<Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0">
<ListBox Name="ListBox1"
ItemsSource="{Binding}"
HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Height="50"
BorderBrush="Black"
BorderThickness="1"
CornerRadius="5"
Padding="3">
<TextBlock Text="{Binding}" />
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Border>
<Border Grid.Column="1">
<ScrollViewer VerticalScrollBarVisibility="Visible"
HorizontalScrollBarVisibility="Disabled"
ScrollChanged="OnScrollChanged"
Name="ScrollViewer1">
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Height="250"
BorderBrush="Black"
BorderThickness="1"
CornerRadius="5"
Padding="3">
<TextBlock Text="{Binding}" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Border>
</Grid>
</Grid>
</Window>
Code:
public partial class Window4 : Window
{
public Window4()
{
InitializeComponent();
DataContext = Enumerable.Range(1, 25);
}
private void OnScrollChanged(object sender, ScrollChangedEventArgs e)
{
var element = ScrollViewer1.InputHitTest(new Point(5, 5));
if (element != null)
{
if (element is FrameworkElement)
{
ListBox1.SelectedItem = (element as FrameworkElement).DataContext;
}
}
}
}
NOTE:
This is just a sample code. Just one of possible ways to do it. And it is not a very healthy piece of code. Some refactoring might be needed. I would wrap this logic up in an Attached Property or a Behavior.
I would use a Scrollbar control and use it somehow like an up/down button. If you move the scroll up you go to the next control and the same moving down.
Not sure if you know what I mean, let me know.

How to add scrolling buttons to a data bound, horiztonal list

So I have a List, which contains items. Right now they are thumbnails of pictures. I wanted this list to be bound to a changing list in code behind, so I used a Listbox. However, I needed this box to flow horizontally. So it is styled as a StackPanel. Lastly, I want buttons to control the scrolling, not scrollbars. That's the part that does not work Here's a code sample :
<UserControl x:Class="TestBench.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<Style x:Key="StackHorz" TargetType="ListBox">
<Style.Setters>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" Background="AliceBlue" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBox">
<ScrollViewer BorderBrush="DarkGreen" BorderThickness="2" VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Disabled">
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<Button x:Name="_Next" Content="NEXT" Height="20" Width="40" VerticalAlignment="Bottom" HorizontalAlignment="Right"/>
<Button x:Name="_Prev" Content="PREV" Height="20" Width="40" VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
<ListBox x:Name="TestList" Height="100" Width="800" VerticalAlignment="Top">
...Insert ListItems...
</ListBox>
</Grid>
In this example the listbox is not bound, but I do need to be able to set ItemsSource={Binding Content}. Code behind I tried was :
namespace TestBench
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
TestList.Style = this.Resources["StackHorzTop"] as Style;
_Next.Click += new RoutedEventHandler(_Next_Click);
_Prev.Click += new RoutedEventHandler(_Prev_Click);
}
void _Prev_Click(object sender, RoutedEventArgs e)
{
TestList.ScrollIntoView(TestList.Items[0]);
}
void _Next_Click(object sender, RoutedEventArgs e)
{
TestList.ScrollIntoView(TestList.Items[TestList.Items.Count - 1]);
}
}
}
But the ScrollIntoView does nothing. I also tried getting the ScrollViewer as a VisualTreeHelper.GetChild() of the list box, but scrolling there using ScrollToHorizontalOffset() does nothing either.
I know it's a weird way to set things up, but I need all 3 functionalities (Binding, Horizontal orientation, No scroll bars with button scrolling). Anyone know where I am going wrong on this one?
Thanks in advance,
Chart.
Maybe you can try putting your listbox in a ScrollViewer and style the ScrollViewer so that the scroll bars are not visible (only the buttons).
More info on the ScrollViewer's templatable parts can be found here.

Resources