WPF 使用按钮和滑块进行放大和缩小

WPF Zoom in and out with both button and slider

有没有办法 link(如果这是正确的词)滑块按钮?我正在尝试创建一个能够缩放图像的图像查看器。我能够通过使用滑块将 Tick 设置为 .25、.50 一直到 2 来实现这一点,这将是 25% 到 200% 的图像比例。但我还想实现一个放大和缩小按钮(就像 Microsoft Paint),单击该按钮会使滑块箭头移动到下一个或上一个刻度(取决于放大或缩小),从而增加或减少图像比例25%。我是 WPF 的新手,所以我不熟悉我可以使用的所有属性,但我读到我可以使用 Storyboard 为它制作动画。除了使用情节提要之外,还有另一种方法可以完成我打算做的事情。现在我的 ZoomIn 函数只是一个虚拟函数并抛出一个异常,因为我仍在研究如何做到这一点。非常感谢您的帮助。

XAML:

<Window x:Class="kazoom.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Zoom Window">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition Height="auto"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition Width="auto"/>
    </Grid.ColumnDefinitions>
    <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
        <Grid x:Name="GridLoadedImage" HorizontalAlignment="Left" VerticalAlignment="Top">
            <Grid.LayoutTransform>
                <ScaleTransform ScaleX="{Binding ElementName=slider1, Path=Value}" ScaleY="{Binding ElementName=slider1, Path=Value}"/>
            </Grid.LayoutTransform>
            <Image Name="loaded" Grid.Column="1" Source="image/Desert.jpg" Stretch="Fill" />
        </Grid>
    </ScrollViewer>
    <StackPanel Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center">
        <Button x:Name="Import" Width="93" Height="20" Content="Import" Margin="10" />
        <Button x:Name="ZoomIn" Width="93" Height="20" Content="ZoomIn" Margin="10" />
    </StackPanel>

    <Slider Name="slider1"
            Grid.Row="1"
            Maximum="2"
            Minimum=".15"
            IsMoveToPointEnabled="True"
            TickPlacement="BottomRight"
            TickFrequency="2"
            Ticks=".15,.30,.45,.60,.75,1,1.15,1.30,1.45,1.60,1.75,2"
            Width="200"
            Margin="20"
            HorizontalAlignment="Center"
            SelectionStart="1" IsSnapToTickEnabled="True"/>

</Grid>
</Window>

隐藏代码:

using Microsoft.Win32;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace kazoom
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {

    public MainWindow()
    {
        InitializeComponent();
        ZoomIn.AddHandler(Button.ClickEvent, new RoutedEventHandler(ZoomIn_Click));
        Import.AddHandler(Button.ClickEvent, new RoutedEventHandler(Import_Click));

    }

    private void Import_Click(object sender, RoutedEventArgs e)
    {
        OpenFileDialog image = new OpenFileDialog();
        image.FileName = "Picture";
        image.Filter = "Pictures (*.jpeg)|*.jpg";

        if (image.ShowDialog() == true)
        {
            loaded.Source = new BitmapImage(new Uri(image.FileName));
        }
    }
    private void ZoomIn_Click(object sender, RoutedEventArgs e)
    {
        throw new System.NotImplementedException();
    }
}

}

我承认,我不知道解决此问题的最佳方法是什么。但是,确实想到了一个相当明显的解决方案:将值绑定到作为当前刻度索引的 int 属性,使用转换器在滑块值和索引之间进行映射,然后让按钮调整 int 属性。例如……

转换器:

class IndexToSliderValueConverter : DependencyObject, IValueConverter
{
    public Slider Slider
    {
        get { return (Slider)GetValue(SliderProperty); }
        set { SetValue(SliderProperty, value); }
    }

    public static readonly DependencyProperty SliderProperty = DependencyProperty.Register(
        "Slider", typeof(Slider), typeof(IndexToSliderValueConverter));

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (Slider != null && Slider.Ticks != null && Slider.Ticks.Count > 0 && value is int)
        {
            int intValue = (int)value;

            if (intValue >= 0 && intValue < Slider.Ticks.Count)
            {
                return Slider.Ticks[(int)value];
            }

            throw new ArgumentOutOfRangeException("value must be valid tick index");
        }

        return Binding.DoNothing;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (Slider != null && Slider.Ticks != null && Slider.Ticks.Count > 0 && value is double)
        {
            double doubleValue = (double)value,
                minDifference = double.MaxValue;
            int index = -1;

            for (int i = 0; i < Slider.Ticks.Count; i++)
            {
                double difference = Math.Abs(doubleValue - Slider.Ticks[i]);

                if (difference < minDifference)
                {
                    index = i;
                    minDifference = difference;
                }
            }

            return index;
        }

        return Binding.DoNothing;
    }
}

上面的转换器在设置目标时简单地使用索引来查找刻度值。在相反的方向上,它会寻找最接近当前滑块值的刻度值,并将滑块值映射到该刻度值的索引(在您的例子中,IsSnapToTickEnabled 设置为 true,滑块值应始终完全等于刻度值,但此实现比假设始终如此的实现更可靠)。

这里的问题是,虽然转换器需要访问实际的 Slider 对象(或至少它的 Ticks 集合),但我不知道有什么方法可以传递实例将转换器用于实际转换方法的对象。

BindingConverterParameter 属性 不是依赖项 属性,因此它本身不能被绑定。而且我无法在其资源声明中将 Slider 元素绑定到转换器,因为即使我命名 Slider,生成的字段的值在转换器对象时仍然是 null已创建。

所以这部分内容有点乱。您会注意到我确实在转换器上实现了依赖项 属性 以获取 Slider 值(以防我想出基于 XAML 的方法来绑定正确的值) .现在,我只是在 window 的代码隐藏中设置该值。

顺便说一下,它看起来像这样:

public partial class MainWindow : Window
{
    public static readonly DependencyProperty SliderIndexValueProperty = DependencyProperty.Register(
        "SliderIndexValue", typeof(int), typeof(MainWindow));

    public int SliderIndexValue
    {
        get { return (int)GetValue(SliderIndexValueProperty); }
        set { SetValue(SliderIndexValueProperty, value); }
    }
    public MainWindow()
    {
        InitializeComponent();

        ((IndexToSliderValueConverter)Resources["indexToSliderValueConverter1"]).Slider = slider1;
        SliderIndexValue = 2;
    }

    private void DownButton_Click(object sender, RoutedEventArgs e)
    {
        if (SliderIndexValue > 0)
        {
            SliderIndexValue--;
        }
    }

    private void UpButton_Click(object sender, RoutedEventArgs e)
    {
        if (SliderIndexValue < slider1.Ticks.Count - 1)
        {
            SliderIndexValue++;
        }
    }
}

在这里,有一个依赖项 属性 表示报价指数。在构造函数中,我通过从资源字典中检索对象并直接分配 属性,将 slider1 对象引用分配给转换器的 Slider 属性。不是很优雅,但它可以按照我需要的方式连接起来。

我还添加了一个分配给报价索引 属性 的方法,就像 "proof of concept" 一样。当然,它有效的事实也足以证明这一点。 :)

最后,XAML 为 window:

<Window x:Class="TestSO28574624SliderWithUpDownButtons.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TestSO28574624SliderWithUpDownButtons"
        x:Name="root"
        Title="MainWindow" Height="350" Width="525">
  <Window.Resources>
    <local:IndexToSliderValueConverter x:Key="indexToSliderValueConverter1"/>
  </Window.Resources>

  <StackPanel>
    <StackPanel Orientation="Horizontal">
      <StackPanel.Resources>
        <Style TargetType="Button">
          <Setter Property="Margin" Value="3" />
        </Style>
      </StackPanel.Resources>
      <Button Content="Down" Click="DownButton_Click"/>
      <Button Content="Up" Click="UpButton_Click" />
    </StackPanel>
    <Slider x:Name="slider1" Minimum="0.15" Maximum="2" IsSnapToTickEnabled="True"
            Ticks="0.15,0.3,0.45,0.6,0.75,1,1.15,1.3,1.45,1.6,1.75,2"
            Value="{Binding ElementName=root, Path=SliderIndexValue, Converter={StaticResource indexToSliderValueConverter1}}"/>
    <TextBlock Text="{Binding ElementName=root, Path=SliderIndexValue}"/>
  </StackPanel>
</Window>

只有几个按钮可以向上或向下移动滑块,滑块本身,以及一个 TextBlock 来显示实际的刻度索引 属性 值(再次说明,为了说明......你显然不需要在你自己的程序中。

您应该能够在自己的代码中使用与上述相同的技术。

郑重声明:我不知道基于 Storyboard 的实现如何工作。我不清楚 Storyboard 如何以有助于调整它的方式访问 Ticks 属性 和相对于当前 Slider 值的索引.但是,我自己对 WPF 还是很陌生。也许其他人会按照这些思路提出一些具体建议。

与此同时,我确信上述方法有效。 :)

您只需枚举滑块的 Ticks 集合即可找到当前滑块值之前或之后的下一个刻度值(取决于您是放大还是缩小)。

private void ZoomIn_Click(object sender, RoutedEventArgs e)
{
    slider1.Value = slider1.Ticks.Select(x => (double?) x)
        .FirstOrDefault(x => x > slider1.Value) ?? slider1.Value;
}

private void ZoomOut_Click(object sender, RoutedEventArgs e)
{
    slider1.Value = slider1.Ticks.Select(x => (double?) x)
        .LastOrDefault(x => x < slider1.Value) ?? slider1.Value;
}

这假定 Ticks 集合中的值当然是按升序存储的。在每个 Select 之前添加一个 OrderBy 方法调用可以防止值的顺序不同。

我能够通过简单地将 slider1 值递增和递减 0.15 并通过添加 1.0 来找到解决方案,它也能够进行 100% 缩放。按钮和滑块都与此解决方案相应地工作,因为每次按下缩放按钮也会移动滑块,这是我正在寻找的效果。下面是代码:

 private void GenericZoomHandler(object sender, RoutedEventArgs e)
    {
        if (sender.Equals(ZoomOut))   //if sender is ZoomOut object
        {
            slider1.Value -= .15;     //decrement by 15%
        }
        else if (sender.Equals(ZoomIn))   //if sender is ZoomIn object
        {
            slider1.Value += .15;    //increment by 15%
        }
        else if (sender.Equals(Zoom100))   //if sender is Zoom100 object
        {
            slider1.Value = 1.0;        //return to original size of image     (100%)
        }

    }