views:

316

answers:

1

I am trying to develop an application that uses a number of images that are stored in a seperate remote file location. The file-paths to the UI elements are stored within the Application Settings. Although I understand how to access the images from Applications Settings using a MultiBinding and a value converter, I'm not sure how to integrate the Multibinding into the ImageButton ControlTemplate below. Can anyone steer me in the right direction?

<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);
    }
}
+1  A: 

Use XAML property element syntax:

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

Note this means your attached properties will need to be strings rather than ImageSources, because they are going to be inputs to your path composition converter.

itowlson
Itowlson - Thank you for your help. I believe I made all revisions as suggested; however returned the following error: System.Windows.Data Error:6: 'TargetDefaultValueConverter' converter failed to convert value 'interface\btn_fullscreen-off_DOWN.png' (type 'String'); fallback value will be used, if available. BindingExpression:Path=(0); DataItem='Button' (Name='btn_Fullscreen_Off'); target element is 'Image' (Name='PART_Image'); target property is 'Source' (type 'ImageSource') IOException:'System.IO.IOException: Cannot locate resource 'interface/btn_fullscreen-off_down.png'. Any thoughts?
Bill
It looks like it's not finding the file. Is the path correct? In your sample you indicate that the path should be `c:\skins\` rather than `interface\`. Step through your IMultiValueConverter in the debugger. Is it receiving the correct values? Is it returning the correct value?
itowlson
Itowlson - Once again, thank you for your advice and assistance. I stepped through IMultiValueConverter as you suggested and found the culprit. So the binding is working, however the images flicker on and off during IsMouseOver/IsPressed/IsEnabled. Could I ask what you would suggest to adapt the code for the IsMouseOver/IsPressed/IsEnabled events? Thanks. Bill
Bill
My guess is that your IMVC is returning new instances of ImageSource every time. So WPF thinks the result has changed and reloads the image. A possible quick fix for this is to maintain a dictionary from paths to ImageSource instances, so that you can return the same ImageSource for the same path. Haven't tested this though. http://stackoverflow.com/questions/2530915/wpf-controltrigger-ismouseover may be related though it doesn't seem to have a good answer yet. (If my suggestion works then maybe you could post it as an answer there).
itowlson
Itowlson - will test and report back. Thanks and I very much appreciate the help. Bill
Bill
Itowlson - Although I wish I could say otherwise, unfortunately due to my lack of experience, I am not sure how to impliment your suggestion to "maintain a dictionary from paths to ImageSource instances, so that you can return the same ImageSource for the same path". My assumption is that you are suggesting that a Resource Dictionary is created to provides a hash table / dictionary implementation that contains WPF resources used by components and other elements of a WPF application. May I ask for a little more direction? Sorry for being so thick. Bill
Bill
The idea would be to create a ResourceDictionary (or plain `Dictionary<string, ImageSource>`) as an app-level variable or as a member of the converter class. Do this in code not XAML. Initially the dictionary is empty. When the IMVC has calculated the full path, it looks in the `CachedImagesSources` dictionary to see if it has already loaded an ImageSource for that path. If it has, it returns that ImageSource rather than loading a new one. If it hasn't, it loads a new ImageSource, but before returning it adds it to `CachedImageSources` with the path as the key. Does that help?
itowlson
Yes, thank you. I appreciate your sharing your knowledge itowlson.Bill
Bill
After doing a little more reading/research, I found that Multibindings don't fit into the markup extension syntax with {}'s, so you have to use the more verbose property-element syntax. I will post the change below.Thank you itowlson for your assistance.
Bill
The following change works:<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>
Bill