WPF ScrollViewer: нажмите=нажмите, нажмите и удерживайте / перетащите=прокрутка. Как этого достичь?

Я разрабатываю приложение WPF touch. У меня есть scrollviewer, содержащий кнопки. Я хочу прокручивать дисплей при нажатии-перетаскивать кнопки и вызывать команду кнопки при нажатии. Вот код для начала:

<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>

и модель представления:

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()); }
    }
}
}

правильно:

  1. когда пользователь нажимает на кнопку, появляется окно сообщения с текстом "кнопка нажата!"==>OK

  2. когда пользователь нажимает на кнопку и двигает пальцем (не отпуская), кнопки прокручиваются вверх и вниз ==> NOK

Как я могу добиться прокрутки в ScrollViewer, который содержит кнопки?

Я разрабатываю Visual Studio 2013 в Windows 7, и я нацелен на рабочий стол Windows 7 и планшет Windows 8 с той же кодовой базой. Framework 4.0. Если это действительно необходимо, я могу обновить до 4.5.2 (у нас много пользователей, поэтому это не тривиально модернизировать.)

2 ответов


так как были Button(s) на ListView, для которого мы должны сделать command получить выполняется по назначению. Так вот в чем была хитрость. Я сделал это ниже способом:

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()); }
    }
}

события:

 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);
        }           
    }

по этой ссылке я написал как Drag & Drop работает в отношении событий и их обмен данными.

и я все еще работаю над перечисляя часть.


Ну, это так просто, что мне стыдно, что я не нашел его раньше. Мне пришлось установить свойство PanningMode в ScrollViewer.

такой:

<ScrollViewer PanningMode="VerticalOnly">

код:

<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>

модель представления не изменяется