6

I'm developping a WPF touch application. I have a scrollviewer containing buttons. I want to scroll the display when I touch-drag the buttons, and call the button's command when I tap. Here's some code to get started:

<Window x:Class="wpf_Button_Scroll.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:my="clr-namespace:wpf_Button_Scroll"
    Title="MainWindow" Height="350" Width="200">
<Window.DataContext>
    <my:MyViewModel />
</Window.DataContext>

<Grid>
    <ScrollViewer>
        <ListView ItemsSource="{Binding MyData}" HorizontalAlignment="Stretch">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <Button Content="{Binding}" 
                            Command="{Binding DataContext.MyCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}" 
                            CommandParameter="{Binding}"
                            Margin="5 2" Width="150" Height="50"
                            FontSize="30" />
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </ScrollViewer>
</Grid>
</Window>

And the view model:

namespace wpf_Button_Scroll
{
class MyViewModel
{
    public MyViewModel()
    {
        MyCommand = new ICommandImplementation();
    }

    public string[] MyData
    {
        get
        {
            return new string[]{
                "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", 
                "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty"
            };
        }
    }

    public ICommand MyCommand { get; private set; }

    private class ICommandImplementation : ICommand
    {
        public bool CanExecute(object parameter) { return true; }
        public event EventHandler CanExecuteChanged;
        public void Execute(object parameter) { System.Windows.MessageBox.Show("Button clicked! " + (parameter ?? "").ToString()); }
    }
}
}

Intended behavior:

  1. when the user taps a button, a message box appears with the text "Button clicked!" ==> OK

  2. when the user presses a button and moves his finger (without releasing), the buttons scroll up and down ==> NOK

How can I achieve scrolling in a ScrollViewer that contains buttons?

I'm developping on Visual Studio 2013 on Windows 7, and I'm targeting a Windows 7 desktop and a Windows 8 tablet with the same codebase. Framework 4.0. If it's really necessary, I can upgrade to 4.5.2 (we have many users, so it's not trivial to upgrade).

DonkeyMaster
  • 1,302
  • 4
  • 17
  • 36
  • Is your problem resolved? (I actually expected this to work by default) – H.B. May 13 '16 at 23:32
  • @H.B. yes, my problem is solved. See the accepted answer (which I just edited becuase it wasn't very clear). – DonkeyMaster May 16 '16 at 09:16
  • Possible duplicate of [WPF Scrollviewer on touch screen tablet](http://stackoverflow.com/questions/17294135/wpf-scrollviewer-on-touch-screen-tablet) – DonkeyMaster May 16 '16 at 14:24

2 Answers2

1

Well, this is so simple that I feel embarrassed that I didn't find it earlier. I had to set the PanningMode property on the ScrollViewer.

Like this:

<ScrollViewer PanningMode="VerticalOnly">

Final code:

<Window x:Class="wpf_Button_Scroll.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:my="clr-namespace:wpf_Button_Scroll"
    Title="MainWindow" Height="350" Width="200">
<Window.DataContext>
    <my:MyViewModel />
</Window.DataContext>

<Grid>
    <ScrollViewer PanningMode="VerticalOnly">
        <ListView ItemsSource="{Binding MyData}" HorizontalAlignment="Stretch">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <Button Content="{Binding}" 
                            Command="{Binding DataContext.MyCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}" 
                            CommandParameter="{Binding}"
                            Margin="5 2" Width="150" Height="50"
                            FontSize="30" />
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </ScrollViewer>
</Grid>
</Window>

The View Model doesn't change

DonkeyMaster
  • 1,302
  • 4
  • 17
  • 36
  • where did you put this attribute exactly? – Kylo Ren May 09 '16 at 18:33
  • @KyloRen I clarified my answer – DonkeyMaster May 16 '16 at 09:15
  • ok.. but i'm still stuck with scrolling part in WPF win app... PanningMode="VerticalOnly" is not seems working in dragging.. – Kylo Ren May 16 '16 at 09:39
  • This solution works for me. I can add the whole code if it helps you. But if my solution doesn't work for you, then we have different problems. – DonkeyMaster May 16 '16 at 09:43
  • please post the xaml code .. that will be helpful... thanks in advance – Kylo Ren May 16 '16 at 09:44
  • I read your answer again. There seems to be some confusion. I am NOT interested in drag and drop, only scrolling. Sorry for the mix up. – DonkeyMaster May 16 '16 at 09:55
  • I understood that after you posted your answer a few days back.... :).. ok the code you posted above I'm not able to drag the button or move the scrollbar...can you give some more hints..? – Kylo Ren May 16 '16 at 10:01
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/112041/discussion-between-donkeymaster-and-kylo-ren). – DonkeyMaster May 16 '16 at 10:05
  • This works for me. I have supplied the *full* code to my project. I don't see what more I can do. – DonkeyMaster May 16 '16 at 10:15
  • Yes thanks for that... Just setting the panning mode can't do the trick .. for scrolling I think this sort of logic will work. http://stackoverflow.com/questions/1316251/wpf-listbox-auto-scroll-while-dragging – Kylo Ren May 16 '16 at 10:20
1

Since there were Button(s) in the ListView for which we have to make the command get executed when intended. So that's the tricky part was. I did it in below way:

XAML:

<Window.DataContext>
    <local:MyViewModel />
</Window.DataContext>

<Grid>
    <ScrollViewer>
        <ListView ItemsSource="{Binding MyData}" HorizontalAlignment="Stretch" Name="listview" ScrollViewer.PanningMode="VerticalOnly">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <Button Content="{Binding}"
                        Command="{Binding DataContext.MyCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}" 
                        CommandParameter="{Binding}"
                        Margin="5 2" Width="150" Height="50"
                        FontSize="30" />
                </DataTemplate>
            </ListView.ItemTemplate>
            <ListView.Resources>
                <Style TargetType="Button">
                    <EventSetter Event="PreviewMouseMove" Handler="PreviewMouseMove" />                        
                    <EventSetter Event="Drop" Handler="Drop" />                       
                    <Setter Property="AllowDrop" Value="True" />                        
                </Style>
            </ListView.Resources>
        </ListView>
    </ScrollViewer>
</Grid>

ViewModel:

class MyViewModel
{
    public MyViewModel()
    {
        MyCommand = new ICommandImplementation();
    }

    public ObservableCollection<string> MyData
    {
        get
        {
            return new ObservableCollection<string>(new string[]{
            "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", 
            "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty"
            });
        }
    }

    public ICommand MyCommand { get; private set; }

    private class ICommandImplementation : ICommand
    {
        public bool CanExecute(object parameter) { return true; }
        public event EventHandler CanExecuteChanged;
        public void Execute(object parameter) { System.Windows.MessageBox.Show("Button clicked! " + (parameter ?? "").ToString()); }
    }
}

Events:

 private void Drop(object sender, DragEventArgs e)
    {
        var source = e.Data.GetData("Source") as string;
        if (source != null)
        {
            int newIndex = listview.Items.IndexOf((sender as Button).Content);
            var list = listview.ItemsSource as ObservableCollection<string>;
            list.RemoveAt(list.IndexOf(source));
            list.Insert(newIndex, source);
        }
    }

    private void PreviewMouseMove(object sender, MouseEventArgs e)
    {
        if (e.LeftButton == MouseButtonState.Pressed)
        {
            Task.Factory.StartNew(new Action(() =>
                {
                    Thread.Sleep(500);
                    App.Current.Dispatcher.BeginInvoke(new Action(() =>
                        {
                            if (e.LeftButton == MouseButtonState.Pressed)
                            {                                    
                                var data = new DataObject();
                                data.SetData("Source", (sender as Button).Content);
                                DragDrop.DoDragDrop(sender as DependencyObject, data, DragDropEffects.Move);
                                e.Handled = true;
                            }
                        }), null);
                }), CancellationToken.None);
        }           
    }

In this link I've written how Drag & Drop works in respect of events and their data sharing.

And I'm still working on scrolling part.

Community
  • 1
  • 1
Kylo Ren
  • 8,551
  • 6
  • 41
  • 66
  • 1
    This is half of the answer to a question I didn't ask. https://stackoverflow.com/questions/37008970/wpf-scrollviewer-tap-click-tap-and-hold-drag-scroll-how-to-achieve-this#comment62029981_37122176 – DonkeyMaster May 16 '16 at 14:05
  • @DonkeyMaster... i think not for the tablet but for mouse drag that link will be helpful...I know it is off the topic, but for a instance i really forgot the question after these many days and only remembered the scrolling problem part... thanks for pointing it out :) – Kylo Ren May 16 '16 at 18:10