WPF слайдер с двумя большими пальцами - PullRequest
4 голосов
/ 22 марта 2011

Я пытаюсь создать слайдер с двумя большими пальцами для своего приложения, чтобы использовать его как слайдер диапазона, но у меня возникают проблемы. Основным требованием для меня является получение одного ползунка с отметками и двумя большими пальцами, которые установлены с IsSnapToTickEnabled = "true".

Я нашел несколько примеров ползунков диапазона при поиске справки (например, этот ), но я не смог изменить его, чтобы добавить метки и заставить пальцы щелкать по галочкам. Впрочем, было бы идеально, если бы тиковые метки и привязка работали для ползунка диапазона в ссылке.

Я пытался изменить шаблон ползунка и добавить к нему еще один большой палец, но тогда я не знаю, как получить значение выбранного большого пальца.

Есть ли у кого-нибудь образец слайдера с двумя большими пальцами, помеченными отметками и привязкой к отметке? Все обнаруженные мной образцы ползунков диапазона используют два ползунка друг на друга, и ни один из них не позволяет ставить отметки или привязываться к отметкам.

Спасибо.

Ответы [ 2 ]

18 голосов
/ 04 декабря 2014

Я понимаю, что этому вопросу более трех лет. Тем не менее, я использовал пример слайдера с более чем одним большим пальцем как упражнение, чтобы узнать больше о WPF, и натолкнулся на этот вопрос, когда пытался выяснить, как это сделать. К сожалению, связанный пример, по-видимому, больше не существует (хороший пример того, почему вопросы и ответы StackOverflow не должны использовать ссылки для какой-либо детали, которая важна для вопроса или ответа).

Я просмотрел большое количество примеров и статей по этой теме, и хотя я не нашел ни одного, который бы специально включал тики, там было достаточно информации, чтобы я мог это выяснить. Я нашел одну статью особенно хорошей в том смысле, что она была достаточно ясной и точной, и в то же время раскрыла пару действительно полезных приемов, которые являются ключевыми в выполнении этой задачи.

Мой конечный результат выглядит так:

correct DoubleThumbSlider

Итак, для блага других, которые могут захотеть сделать то же самое или просто хотят лучше понять общие приемы, вот как вы создаете ползунковый регулятор с двумя пальцами, который поддерживает различные функции галочек основного слайдер & hellip;


Отправной точкой является сам класс UserControl. В Visual Studio добавьте новый класс UserControl в проект. Теперь добавьте все свойства, которые вы хотите поддерживать. К сожалению, я не нашел механизма, который позволял бы просто делегировать свойства соответствующему экземпляру (ам) ползунка в UserControl, так что это означает написание новых свойств для каждого из них.

Исходя из предварительных условий (т. Е. Элементов, которые должны быть объявлены другими участниками), одной из функций, которые я хотел, было ограничение перемещения каждого ползунка, чтобы его нельзя было перетаскивать за другой. Я решил реализовать это, используя CoerceValueCallback для свойств, поэтому мне понадобились методы обратного вызова:

private static object LowerValueCoerceValueCallback(DependencyObject target, object valueObject)
{
    DoubleThumbSlider targetSlider = (DoubleThumbSlider)target;
    double value = (double)valueObject;

    return Math.Min(value, targetSlider.UpperValue);
}

private static object UpperValueCoerceValueCallback(DependencyObject target, object valueObject)
{
    DoubleThumbSlider targetSlider = (DoubleThumbSlider)target;
    double value = (double)valueObject;

    return Math.Max(value, targetSlider.LowerValue);
}

В моем случае мне потребовались только Minimum, Maximum, IsSnapToTickEnabled, TickFrequency, TickPlacement и Ticks из нижележащих ползунков и два новых свойства для сопоставления с отдельными значениями ползунка LowerValue и HigherValue. Сначала я должен был объявить DependencyProperty объекты:

public static readonly DependencyProperty MinimumProperty =
    DependencyProperty.Register("Minimum", typeof(double), typeof(DoubleThumbSlider), new UIPropertyMetadata(0d));
public static readonly DependencyProperty LowerValueProperty =
    DependencyProperty.Register("LowerValue", typeof(double), typeof(DoubleThumbSlider), new UIPropertyMetadata(0d, null, LowerValueCoerceValueCallback));
public static readonly DependencyProperty UpperValueProperty =
    DependencyProperty.Register("UpperValue", typeof(double), typeof(DoubleThumbSlider), new UIPropertyMetadata(1d, null, UpperValueCoerceValueCallback));
public static readonly DependencyProperty MaximumProperty =
    DependencyProperty.Register("Maximum", typeof(double), typeof(DoubleThumbSlider), new UIPropertyMetadata(1d));
public static readonly DependencyProperty IsSnapToTickEnabledProperty =
    DependencyProperty.Register("IsSnapToTickEnabled", typeof(bool), typeof(DoubleThumbSlider), new UIPropertyMetadata(false));
public static readonly DependencyProperty TickFrequencyProperty =
    DependencyProperty.Register("TickFrequency", typeof(double), typeof(DoubleThumbSlider), new UIPropertyMetadata(0.1d));
public static readonly DependencyProperty TickPlacementProperty =
    DependencyProperty.Register("TickPlacement", typeof(TickPlacement), typeof(DoubleThumbSlider), new UIPropertyMetadata(TickPlacement.None));
public static readonly DependencyProperty TicksProperty =
    DependencyProperty.Register("Ticks", typeof(DoubleCollection), typeof(DoubleThumbSlider), new UIPropertyMetadata(null));

После этого я мог бы написать сами свойства:

public double Minimum
{
    get { return (double)GetValue(MinimumProperty); }
    set { SetValue(MinimumProperty, value); }
}

public double LowerValue
{
    get { return (double)GetValue(LowerValueProperty); }
    set { SetValue(LowerValueProperty, value); }
}

public double UpperValue
{
    get { return (double)GetValue(UpperValueProperty); }
    set { SetValue(UpperValueProperty, value); }
}

public double Maximum
{
    get { return (double)GetValue(MaximumProperty); }
    set { SetValue(MaximumProperty, value); }
}

public bool IsSnapToTickEnabled
{
    get { return (bool)GetValue(IsSnapToTickEnabledProperty); }
    set { SetValue(IsSnapToTickEnabledProperty, value); }
}

public double TickFrequency
{
    get { return (double)GetValue(TickFrequencyProperty); }
    set { SetValue(TickFrequencyProperty, value); }
}

public TickPlacement TickPlacement
{
    get { return (TickPlacement)GetValue(TickPlacementProperty); }
    set { SetValue(TickPlacementProperty, value); }
}

public DoubleCollection Ticks
{
    get { return (DoubleCollection)GetValue(TicksProperty); }
    set { SetValue(TicksProperty, value); }
}

Теперь их нужно подключить к базовым элементам управления Slider, которые будут составлять UserControl. Поэтому я добавил два Slider элемента управления с привязками для свойств к соответствующим свойствам в моем UserControl:

<Grid>
  <Slider x:Name="lowerSlider"
          VerticalAlignment="Center"
          Minimum="{Binding ElementName=root, Path=Minimum}"
          Maximum="{Binding ElementName=root, Path=Maximum}"
          Value="{Binding ElementName=root, Path=LowerValue, Mode=TwoWay}"
          IsSnapToTickEnabled="{Binding ElementName=root, Path=IsSnapToTickEnabled}"
          TickFrequency="{Binding ElementName=root, Path=TickFrequency}"
          TickPlacement="{Binding ElementName=root, Path=TickPlacement}"
          Ticks="{Binding ElementName=root, Path=Ticks}"
          />
  <Slider x:Name="upperSlider"
          VerticalAlignment="Center"
          Minimum="{Binding ElementName=root, Path=Minimum}"
          Maximum="{Binding ElementName=root, Path=Maximum}"
          Value="{Binding ElementName=root, Path=UpperValue, Mode=TwoWay}"
          IsSnapToTickEnabled="{Binding ElementName=root, Path=IsSnapToTickEnabled}"
          TickFrequency="{Binding ElementName=root, Path=TickFrequency}"
          TickPlacement="{Binding ElementName=root, Path=TickPlacement}"
          Ticks="{Binding ElementName=root, Path=Ticks}"
          />
</Grid>

Обратите внимание, что здесь я дал моему UserControl имя root и сослался на это в объявлениях Binding. Большинство свойств напрямую переходят к идентичным свойствам в UserControl, но, конечно, отдельные свойства Value для каждого элемента управления Slider сопоставляются с соответствующими свойствами LowerValue и UpperValue UserControl.

Теперь вот самая хитрая часть. Если вы просто остановитесь здесь, вы получите что-то похожее на это: incorrect DoubleThumbSlider Второй объект Slider находится полностью поверх первого, в результате чего его дорожка закрывает первый большой палец Slider. Это не просто визуальная проблема; второй объект Slider, находящийся сверху, получает все щелчки мышью, предотвращая настройку первого Slider.

Чтобы исправить это, я отредактировал стиль для второго ползунка, чтобы удалить те визуальные элементы, которые мешают. Я оставил их для первого ползунка, чтобы обеспечить фактические визуальные эффекты для элемента управления. К сожалению, я не смог найти способ декларативного переопределения только тех частей, которые мне нужно было изменить. Но с помощью Visual Studio вы можете создать полную копию существующего стиля, которую затем можно отредактировать при необходимости:

  1. Переключитесь в режим «Дизайн» в конструкторе WPF для вашего UserControl
  2. Щелкните правой кнопкой мыши по ползунку и выберите «Редактировать шаблон / Редактировать копию ...» во всплывающем меню

Это так просто. :) Это добавит атрибут Style к объявлению Slider в вашем UserControl XAML, ссылаясь на новый стиль, который вы только что создали.

Элемент управления Slider на самом деле имеет два основных шаблона элемента управления: один для горизонтальной ориентации и один для вертикальной. Я опишу изменения в горизонтальном шаблоне здесь; Я предполагаю, что будет очевидно, как сделать подобные изменения в вертикальном шаблоне.

Я использую функцию «Перейти к определению» в Visual Studio, чтобы быстро добраться до нужной мне части шаблона: найдите атрибут Style в Slider вашего UserControl, нажмите на название стиля и нажмите F12 . Это приведет вас к основному объекту Style, где вы найдете Setter для горизонтального шаблона (вертикальный шаблон управляется Setter в Trigger на основе значения Orientation). Нажмите на название горизонтального шаблона (когда я делал это, это был «SliderHorizontal», но я думаю, что он может измениться, и, конечно, он будет другим для других типов элементов управления).

Как только вы доберетесь до ControlTemplate, удалите все визуальные атрибуты из элементов, которые не должны использоваться. Это означает удаление некоторых элементов и удаление Background, BorderBrush, BorderThickness, Fill и т. Д. Из элементов, которые вы не можете полностью удалить. В моем случае я полностью удалил RepeatButton и изменил другие необходимые элементы, чтобы они не отображались и не занимали места (чтобы они не получали щелчки мыши). Я завелся с этим:

<ControlTemplate x:Key="SliderHorizontal" TargetType="{x:Type Slider}">
  <Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True">
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto" MinHeight="{TemplateBinding MinHeight}"/>
        <RowDefinition Height="Auto"/>
      </Grid.RowDefinitions>
      <TickBar x:Name="TopTick" Fill="{TemplateBinding Foreground}" Height="4" Margin="0,0,0,2" Placement="Top" Grid.Row="0" Visibility="Collapsed"/>
      <TickBar x:Name="BottomTick" Fill="{TemplateBinding Foreground}" Height="4" Margin="0,2,0,0" Placement="Bottom" Grid.Row="2" Visibility="Collapsed"/>
      <Border x:Name="TrackBackground" Grid.Row="1" VerticalAlignment="center">
        <Canvas>
          <Rectangle x:Name="PART_SelectionRange" />
        </Canvas>
      </Border>
      <Track x:Name="PART_Track" Grid.Row="1">
        <Track.Thumb>
          <Thumb x:Name="Thumb" Focusable="False" Height="18" OverridesDefaultStyle="True" Template="{StaticResource SliderThumbHorizontalDefault}" VerticalAlignment="Center" Width="11"/>
        </Track.Thumb>
      </Track>
    </Grid>
  </Border>
  <!-- I left the ControlTemplate.Triggers element just as it was, no changes -->
</ControlTemplate>

И это все, что нужно было. :)

И еще одна вещь: вышеизложенное предполагает, что стиль акции для Slider не изменится. То есть стиль второго слайдера был скопирован и жестко закодирован в программу, но этот жестко запрограммированный стиль все еще зависит от макета стиля, из которого он был скопирован. Если этот стиль поменяется, то расположение первого ползунка может измениться так, чтобы второй ползунок больше не выстраивался в линию или не выглядел правильно.

Если это проблема, то вы можете подойти к шаблону несколько иначе: вместо изменения шаблона SliderHorizontal сделайте его копию и Style, который ссылается на него, изменив имена обоих и изменив копия Style, так что она ссылается на скопированный шаблон вместо оригинала. Затем вы просто изменяете копию и устанавливаете стиль первого Slider на неизмененный Style, а стиль второго Slider на измененный.

Помимо техник, продемонстрированных здесь, другие могут захотеть сделать что-то немного по-другому. Например, я полностью отбросил кнопки повтора, что означает, что вы можете только перетаскивать большой палец. Нажатие на дорожку за пределами большого пальца не влияет на это. Кроме того, большие пальцы по-прежнему работают так же, как и в базовом элементе управления Slider, при этом середина большого пальца является индикатором того, где находится значение большого пальца. Это означает, что когда вы перетаскиваете один большой палец на другой, они оказываются со вторым большим пальцем сверху первого (т.е. первый большой палец не будет перетаскиваться, пока вы не переместите второй большой палец, чтобы увидеть первый).

Изменение этого поведения не должно быть слишком сложным, но оно требует дополнительной работы. Вы можете добавить поле для больших пальцев, чтобы они не накладывались друг на друга (но затем вы также захотите изменить форму большого пальца при отображении отметок, а также отрегулировать поле дорожки, чтобы все по-прежнему выравнивалось) , Вместо того, чтобы удалять кнопки повтора, вы можете оставить их на месте, но отрегулировать их положение так, чтобы они работали так, как вы хотите, двумя большими пальцами.

Я оставляю эти задания в качестве упражнения для читателя. :)

2 голосов
/ 31 марта 2011

Я закончил тем, что изменил ссылку в моем исходном посте.Я скомбинировал его с TickBar и сам провел расчеты, чтобы обеспечить функциональность привязки к тикам.

...