Мультисвязывание путей к файлам в шаблоне управления кнопками

Я пытаюсь разработать приложение, которое использует ряд изображений, хранящихся в отдельном удаленном файле. Пути к файлам элементов пользовательского интерфейса хранятся в настройках приложения. Хотя я понимаю, как получить доступ к изображениям из настроек приложений с помощью MultiBinding и преобразователя значений, я не уверен, как интегрировать Multibinding в ImageButton ControlTemplate ниже. Может ли кто-нибудь направить меня в правильном направлении?

<Image.Source>
     <MultiBinding Converter="{StaticResource MyConverter}">
         <Binding Source="{StaticResource Properties.Settings}" Path="Default.pathToInterfaceImages" />
         <Binding Source="ScreenSaver.png"></Binding>
     </MultiBinding>
</Image.Source>

<Button Click="btn_ScreenSaver_Click" Style="{DynamicResource ThreeImageButton}"
               local:ThreeImageButton.Image="C:\Skins\ScreenSaver_UP.png"
               local:ThreeImageButton.MouseOverImage="C:\Skins\ScreenSaver_OVER.png" 
               local:ThreeImageButton.PressedImage="C:\Skins\ScreenSaver_DOWN.png"/>

<Style 
    x:Key="ThreeImageButton"
    TargetType="{x:Type Button}">
    <Setter Property="FontSize" Value="10"/>
    <Setter Property="Height" Value="34"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Button}">
                <StackPanel Orientation="Horizontal" >
                    <Image Name="PART_Image" Source= "{Binding Path=(local:ThreeImageButton.Image), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}" />
                </StackPanel>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter Property="Source" Value="{Binding Path=(local:ThreeImageButton.MouseOverImage), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}" TargetName="PART_Image"/>
                    </Trigger>
                    <Trigger Property="IsPressed" Value="True">
                        <Setter Property="Source" Value="{Binding Path=(local:ThreeImageButton.PressedImage), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}" TargetName="PART_Image"/>
                    </Trigger>
                    <Trigger Property="IsEnabled" Value="False">
                        <Setter Property="Source" Value="{Binding Path=(local:ThreeImageButton.Image), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}" TargetName="PART_Image"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

public class ThreeImageButton : DependencyObject
{
    // Add three new Dependency Properties to the Button Class to hold the 
    // path to each of the images that are bound to the control, displayed 
    // during normal, mouse-over and pressed states.
    public static readonly DependencyProperty ImageProperty;
    public static readonly DependencyProperty MouseOverImageProperty;
    public static readonly DependencyProperty PressedImageProperty;

    public static ImageSource GetImage(DependencyObject obj)
    { return (ImageSource)obj.GetValue(ImageProperty); }

    public static ImageSource GetMouseOverImage(DependencyObject obj)
    { return (ImageSource)obj.GetValue(MouseOverImageProperty); }

    public static ImageSource GetPressedImage(DependencyObject obj)
    { return (ImageSource)obj.GetValue(PressedImageProperty); }

    public static void SetImage(DependencyObject obj, ImageSource value)
    { obj.SetValue(ImageProperty, value); }

    public static void SetMouseOverImage(DependencyObject obj, ImageSource value)
    { obj.SetValue(MouseOverImageProperty, value); }

    public static void SetPressedImage(DependencyObject obj, ImageSource value)
    { obj.SetValue(PressedImageProperty, value); }

    // Register each property with the control.
    static ThreeImageButton()
    {
        var metadata = new FrameworkPropertyMetadata((ImageSource)null);
        ImageProperty = DependencyProperty.RegisterAttached("Image", typeof(ImageSource), typeof(ThreeImageButton), metadata);
        var metadata1 = new FrameworkPropertyMetadata((ImageSource)null);
        MouseOverImageProperty = DependencyProperty.RegisterAttached("MouseOverImage", typeof(ImageSource), typeof(ThreeImageButton), metadata1);
        var metadata2 = new FrameworkPropertyMetadata((ImageSource)null);
        PressedImageProperty = DependencyProperty.RegisterAttached("PressedImage", typeof(ImageSource), typeof(ThreeImageButton), metadata2);
    }
}

person Bill    schedule 27.03.2010    source источник


Ответы (1)


Используйте синтаксис элемента свойства XAML:

<ControlTemplate TargetType="{x:Type Button}">
  <StackPanel Orientation="Horizontal" >
    <Image>
      <Image.Source>
        <MultiBinding Converter="{StaticResource MyConverter}">
          <Binding Source="{StaticResource Properties.Settings}"
                   Path="Default.pathToInterfaceImages" />
          <Binding Path="(local:ThreeImageButton.Image)"
                   RelativeSource="{RelativeSource TemplatedParent}" />
        </MultiBinding>
      </Image.Source>
    </Image>
  </StackPanel>
</ControlTemplate>

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

person itowlson    schedule 27.03.2010
comment
Итоулсон - Спасибо за помощь. Я считаю, что сделал все исправления, как было предложено; однако возвратил следующую ошибку: System.Windows.Data Error:6: преобразователю «TargetDefaultValueConverter» не удалось преобразовать значение «interface\btn_fullscreen-off_DOWN.png» (тип «String»); будет использовано резервное значение, если оно доступно. BindingExpression:Путь=(0); DataItem='Кнопка' (Name='btn_Fullscreen_Off'); целевой элемент — «Изображение» (Name = «PART_Image»); целевое свойство — «Источник» (тип «ImageSource») IOException: «System.IO.IOException: не удается найти ресурс «interface/btn_fullscreen-off_down.png». Есть предположения? - person Bill; 28.03.2010
comment
Похоже, он не находит файл. Путь правильный? В вашем образце вы указываете, что путь должен быть c:\skins` rather than interface`. Пройдитесь по вашему IMultiValueConverter в отладчике. Получает ли он правильные значения? Возвращает правильное значение? - person itowlson; 28.03.2010
comment
Itowlson - Еще раз спасибо за ваши советы и помощь. Я прошел через IMultiValueConverter, как вы предложили, и нашел виновника. Таким образом, привязка работает, однако изображения мерцают во время IsMouseOver/IsPressed/IsEnabled. Могу я спросить, что бы вы предложили для адаптации кода для событий IsMouseOver/IsPressed/IsEnabled? Спасибо. Счет - person Bill; 28.03.2010
comment
Я предполагаю, что ваш IMVC каждый раз возвращает новые экземпляры ImageSource. Поэтому WPF считает, что результат изменился, и перезагружает изображение. Возможное быстрое решение этой проблемы — поддерживать словарь из путей к экземплярам ImageSource, чтобы вы могли возвращать один и тот же ImageSource для одного и того же пути. Хотя не проверял это. stackoverflow.com/questions/2530915/ может быть связано, хотя и не Кажется, у меня еще нет хорошего ответа. (Если мое предложение сработает, возможно, вы могли бы опубликовать его там как ответ). - person itowlson; 29.03.2010
comment
Itowlson - проверит и отчитается. Спасибо, и я очень ценю помощь. Счет - person Bill; 29.03.2010
comment
Итоулсон. Хотя я хотел бы сказать иначе, к сожалению, из-за отсутствия у меня опыта я не уверен, как реализовать ваше предложение поддерживать словарь из путей к экземплярам ImageSource, чтобы вы могли вернуть тот же источник изображения для того же пути. Я предполагаю, что вы предлагаете создать словарь ресурсов для предоставления реализации хеш-таблицы/словаря, которая содержит ресурсы WPF, используемые компонентами и другими элементами приложения WPF. Могу я попросить немного больше направления? Извините, что так толсто. Счет - person Bill; 30.03.2010
comment
Идея состоит в том, чтобы создать ResourceDictionary (или обычный Dictionary<string, ImageSource>) как переменную уровня приложения или как член класса преобразователя. Сделайте это в коде, а не в XAML. Изначально словарь пуст. Когда IMVC вычислит полный путь, он просматривает словарь CachedImagesSources, чтобы убедиться, что он уже загрузил ImageSource для этого пути. Если это так, он возвращает этот ImageSource, а не загружает новый. Если это не так, он загружает новый ImageSource, но перед возвратом добавляет его в CachedImageSources с путем в качестве ключа. Это помогает? - person itowlson; 31.03.2010
comment
Да спасибо. Я ценю, что вы поделились своими знаниями, itowlson. Счет - person Bill; 31.03.2010
comment
Проведя еще немного чтения/исследований, я обнаружил, что Multibindings не вписываются в синтаксис расширения разметки с {}, поэтому вам нужно использовать более подробный синтаксис элемента свойства. Я опубликую изменение ниже. Спасибо itowlson за вашу помощь. - person Bill; 01.04.2010
comment
Следующее изменение работает: ‹Trigger Property=IsMouseOver Value=True› ‹Setter Property=Source TargetName=PART_Image› ‹Setter.Value› ‹MultiBinding Converter={StaticResource MyConverter}› ‹Binding Source={StaticResource Properties.Settings} Path=Default .pathToBaseImageDirectory /› ‹Binding RelativeSource={RelativeSource TemplatedParent} Path=(local:ThreeImageButton.MouseOverImage)/› ‹/MultiBinding› ‹/Setter.Value› ‹/Setter› ‹/Trigger› - person Bill; 01.04.2010