WPF 自定义控件:TemplateBinding 到图像

发布于 2024-08-15 10:18:59 字数 3921 浏览 11 评论 0原文

我正在创建一个 WPF 自定义控件,一个带有 ImageTextButton。我向控件添加了两个依赖属性:ImagePathText,并且控件模板(在 Themes\Generic.xaml 中)是一个简单的堆栈面板,用于排列图像和水平文本。

Text 属性工作正常。但由于某种原因,当我使用 TemplateBindingImagePath 依赖属性来获取其路径时,我的测试项目中的示例图像没有出现。我通过暂时用图像的路径替换自定义控件中的 TemplateBinding 来测试图像,在这种情况下它会出现。

我希望在这方面有更多经验的人可以看一下并告诉我为什么控件不能按预期工作。感谢您的帮助。

我的 VS 2008 解决方案包含一个项目 CustomControlDemo。该项目包含一个自定义控件 TaskButton.cs 和一个主窗口 Window1.xaml,我用它来测试该控件。我的测试图像 calendar.png 位于项目根级别的 Resources 文件夹中,Generic.xaml 位于同样位于项目根级别的 Themes 文件夹中。

这是我的自定义控件的代码(来自 TaskButton.cs):

using System.Windows;
using System.Windows.Controls;

namespace CustomControlDemo
{
    public class TaskButton : RadioButton
    {
        #region Fields

        // Dependency property backing variables
        public static readonly DependencyProperty ImagePathProperty;
        public static readonly DependencyProperty TextProperty;

        #endregion

        #region Constructors

        /// <summary>
        /// Default constructor.
        /// </summary>
        static TaskButton()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(TaskButton), new FrameworkPropertyMetadata(typeof(TaskButton)));

            // Initialize ImagePath dependency properties
            ImagePathProperty = DependencyProperty.Register("ImagePath", typeof(string), typeof(TaskButton), new UIPropertyMetadata(null));
            TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(TaskButton), new UIPropertyMetadata(null));
        }

        #endregion

        #region Dependency Property Wrappers

        /// <summary>
        /// The ImagePath dependency property.
        /// </summary>
        public string ImagePath
        {
            get { return (string)GetValue(ImagePathProperty); }
            set { SetValue(ImagePathProperty, value); }
        }

        /// <summary>
        /// The Text dependency property.
        /// </summary>
        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }

        #endregion
    }
}

这是控件模板(来自 Generic.xaml):

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:CustomControlDemo">


    <Style TargetType="{x:Type local:TaskButton}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:TaskButton}">
                    <StackPanel Height="Auto" Orientation="Horizontal">
                        <Image Source="{TemplateBinding ImagePath}"  Width="24" Height="24" Stretch="Fill"/>
                        <TextBlock Text="{TemplateBinding Text}"  HorizontalAlignment="Left" Foreground="{DynamicResource TaskButtonTextBrush}" FontWeight="Bold"  Margin="5,0,0,0" VerticalAlignment="Center" FontSize="12" />
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

最后,这是我用来测试控件的 Window1 标记:

<Window x:Class="CustomControlDemo.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:customControl="clr-namespace:CustomControlDemo"
    Title="Window1" Height="300" Width="300">
    <Grid>
        <customControl:TaskButton ImagePath="Resources\calendar.png" Text="Calendar" />
    </Grid>
</Window>

任何想法为什么图像路径不起作用?再次感谢。

I am creating a WPF custom control, a Button with an Image and Text. I have added two dependency properties to the control, ImagePath and Text, and the control template (in Themes\Generic.xaml) is a simple stack panel that arranges the image and text horizontally.

The Text property works fine. But for some reason, the sample image in my test project doesn't appear when I use TemplateBinding to the ImagePath dependency property to get its path. I have tested the image by temporarily replacing the TemplateBinding in the custom control with a path to the image, in which case it appears.

I am hoping that someone with more experience in this area can take a look and tell me why the control isn't working as expected. Thanks for your help.

My VS 2008 solution contains one project, CustomControlDemo. The project contains a custom control, TaskButton.cs, and a main window, Window1.xaml, that I use to test the control. My test image, calendar.png, is located in a Resources folder at the root level of the project, and Generic.xaml is located in a Themes folder, also at the root level of the project.

Here is the code for my custom control (from TaskButton.cs):

using System.Windows;
using System.Windows.Controls;

namespace CustomControlDemo
{
    public class TaskButton : RadioButton
    {
        #region Fields

        // Dependency property backing variables
        public static readonly DependencyProperty ImagePathProperty;
        public static readonly DependencyProperty TextProperty;

        #endregion

        #region Constructors

        /// <summary>
        /// Default constructor.
        /// </summary>
        static TaskButton()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(TaskButton), new FrameworkPropertyMetadata(typeof(TaskButton)));

            // Initialize ImagePath dependency properties
            ImagePathProperty = DependencyProperty.Register("ImagePath", typeof(string), typeof(TaskButton), new UIPropertyMetadata(null));
            TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(TaskButton), new UIPropertyMetadata(null));
        }

        #endregion

        #region Dependency Property Wrappers

        /// <summary>
        /// The ImagePath dependency property.
        /// </summary>
        public string ImagePath
        {
            get { return (string)GetValue(ImagePathProperty); }
            set { SetValue(ImagePathProperty, value); }
        }

        /// <summary>
        /// The Text dependency property.
        /// </summary>
        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }

        #endregion
    }
}

And here is the control template (from Generic.xaml):

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:CustomControlDemo">


    <Style TargetType="{x:Type local:TaskButton}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:TaskButton}">
                    <StackPanel Height="Auto" Orientation="Horizontal">
                        <Image Source="{TemplateBinding ImagePath}"  Width="24" Height="24" Stretch="Fill"/>
                        <TextBlock Text="{TemplateBinding Text}"  HorizontalAlignment="Left" Foreground="{DynamicResource TaskButtonTextBrush}" FontWeight="Bold"  Margin="5,0,0,0" VerticalAlignment="Center" FontSize="12" />
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

And finally, here is the Window1 markup that I am using to test the control:

<Window x:Class="CustomControlDemo.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:customControl="clr-namespace:CustomControlDemo"
    Title="Window1" Height="300" Width="300">
    <Grid>
        <customControl:TaskButton ImagePath="Resources\calendar.png" Text="Calendar" />
    </Grid>
</Window>

Any ideas why the image path isn't working? Thanks again.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

泪之魂 2024-08-22 10:18:59

我将把 CWAP 的答案保留为已接受的答案,因为它在技术上是正确的。然而,事实证明有一种更简单的方法可以解决这个问题。

TemplateBindings 不是一流的 Binding 对象。它们被设计为轻量级的,因此它们是单向的,并且缺乏其他 Binding 对象的一些功能。最值得注意的是,它们不支持与目标关联的已知类型转换器。请参阅 MacDonald,C# 2008 中的 Pro WPF,第 14 页。 872. 这就是为什么 CWAP 正确响应,我可能需要创建一个类型转换器并在自定义按钮的控件模板中专门引用它。

但我不必使用 TemplateBinding 将控件模板绑定到自定义控件的 ImagePath 属性。我可以使用普通的旧 Binding 对象。以下是我的自定义控件模板的修订后的标记:

<!-- Task Button Default Control Template-->
<Style TargetType="{x:Type local:TaskButton}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:TaskButton}">
                <StackPanel Height="Auto" Orientation="Horizontal">
                    <Image Source="{Binding Path=ImagePath, RelativeSource={RelativeSource TemplatedParent}}" Width="24" Height="24" Stretch="Fill" Margin="10,0,0,0" />
                    <TextBlock Text="{TemplateBinding Text}"  HorizontalAlignment="Left" Foreground="{TemplateBinding Foreground}" FontWeight="Bold"  Margin="5,0,10,0" VerticalAlignment="Center" FontSize="12" />
                </StackPanel>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

如果您查看模板中的 ImageControl,您可以看到更改。请注意同一对象中的RelativeSource 属性。将此属性设置为 ={RelativeSource TemplatedParent} 可以让我在 TaskButton 的 Window1 实例中输入相对路径,并在自定义控件中正确解析它。

因此,我对研究此线程的其他人的建议是跳过值转换器,只需从 TemplateBinding 切换到 Image 属性的 Binding。

还要感谢 Marco Zhou,他提供了 此答案 MSDN WPF 论坛中的类似问题。

I am going to leave cwap's answer as the accepted answer, because it is technically correct. However, it turns out that there is an easier way to solve this problem.

TemplateBindings aren't first-class Binding objects. They are designed to be lightweight, so they are one-way, and they lack some features of other Binding objects. Most notably, they don't support known type converters associated with a target. See MacDonald, Pro WPF in C# 2008, p. 872. That's why cwap responds correctly that I would probably need to create a type converter and reference it specifically in the control template for my custom button.

But I don't have to use a TemplateBinding to bind the control template to the ImagePath property of my custom control. I can use a plain old Binding object. Here is the revised markup for my custom control's template:

<!-- Task Button Default Control Template-->
<Style TargetType="{x:Type local:TaskButton}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:TaskButton}">
                <StackPanel Height="Auto" Orientation="Horizontal">
                    <Image Source="{Binding Path=ImagePath, RelativeSource={RelativeSource TemplatedParent}}" Width="24" Height="24" Stretch="Fill" Margin="10,0,0,0" />
                    <TextBlock Text="{TemplateBinding Text}"  HorizontalAlignment="Left" Foreground="{TemplateBinding Foreground}" FontWeight="Bold"  Margin="5,0,10,0" VerticalAlignment="Center" FontSize="12" />
                </StackPanel>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

If you look at the ImageControl in the template, you can see the change. Note the RelativeSource property in the same object. Setting this property to ={RelativeSource TemplatedParent} is what lets me enter a relative path in my Window1 instance of the TaskButton and have it resolved correctly in the custom control.

So my recommendation for others researching this thread would be to skip the value converter and simply switch from TemplateBinding to Binding for the Image property.

Thanks also to Marco Zhou, who provided this answer to a similar question in the MSDN WPF forum.

空袭的梦i 2024-08-22 10:18:59

图像不将字符串作为源:) 您可以在智能感知中看到这一点。您需要绑定 ImageSource (或使用 IValueConverter 将字符串转换为 ImageSource)

请参阅 这个问题,了解有关如何进行此转换的一些提示。

Image doesn't take a string as a source :) You can see this in intellisense. You need to bind on an ImageSource (Or use an IValueConverter to convert the string to an ImageSource)

See this question for some tips on how to do this conversion.

计㈡愣 2024-08-22 10:18:59

事实上,这两个答案都不正确。

{TemplateBinding ImagePath} 只不过是 {Binding Path=ImagePath,relativeSource={RelativeSource TemplatedParent}} 的快捷方式,因此几乎完全相同。

此外,如果您为 ImagePath 提供字符串,它将正确解析为 ImageSource,尽管您的应用程序性能会受到影响。真正的问题与测试的 xaml 中提供的 ImagePath="Resources\calendar.png" 上的相对和绝对图像路径有关。这提示编译器认为提供的路径是绝对路径,因为在定义路径时使用 \ 而不是 / 。

长形式的绑定有效而快捷方式无效的原因是,它向编译器提供了线索:所提供的图像的源 (Resources\calendar.png) 是相对路径而不是绝对路径,因此图像找到并且绑定起作用。如果您调试绑定,您将看到快捷方式尝试将提供的字符串解析为图像源,但找不到文件“Resources\calendar.png”如果您提供图像的完整 URI,即 “C:\ ...\Resources\calendar.png""/application;component/Resources/calendar.png" 的相应混合符号,然后将找到图像并解析绑定。

当您尝试引用来自外部源的图像而不是那些作为资源编译到最终编译中的图像时,这一点变得非常重要。

Actually neither of these answers are correct.

{TemplateBinding ImagePath} is nothing more than a shortcut for {Binding Path=ImagePath, RelativeSource={RelativeSource TemplatedParent}} and as such is almost completely identical.

Also if you provide a string for ImagePath it will correctly resolve to an ImageSource although you take a hit in application performance. The real issue has to do with relative and absolute image path on the supplied ImagePath="Resources\calendar.png" in the xaml for the test. This clues the compiler to think that the supplied path is absolute because of the use of \ instead of / in defining the path.

The reason that the long form of the binding works and the shortcut doesn't is that it provides clues to the compiler that the source of the image supplied (Resources\calendar.png) is a relative path not an absolute path, therefore the image is found and the binding works. If you debug the binding you will see that the shortcut tries resolve the supplied string into an image source but can not find the file "Resources\calendar.png" If you provide a full URI to the image i.e "C:\...\Resources\calendar.png" or the corresponding blend notation of "/application;component/Resources/calendar.png" then the image will be found and the binding resolved.

This point becomes really important when you are trying to reference images from an external source instead of those compiled as resources into the final compilation.

蹲墙角沉默 2024-08-22 10:18:59

简单方法(已测试)
1-使您的 valueConverter 像这样

  public class objectToImageSourceConverter:IValueConverter
    {

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {

            string packUri =value.ToString();
            ImageSource Source = new ImageSourceConverter().ConvertFromString(packUri) as ImageSource;
            return Source;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

2-将您的图像源绑定到父级的字符串属性(我使用“标签”属性)
像这样的xaml:

<Image HorizontalAlignment="Right"  Height="Auto" Margin="0,11.75,5.5,10.75" VerticalAlignment="Stretch" Width="40.997" Source="{Binding Path=Tag, RelativeSource={RelativeSource TemplatedParent}}"/>

simple way(tested)
1-make your valueConverter like this

  public class objectToImageSourceConverter:IValueConverter
    {

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {

            string packUri =value.ToString();
            ImageSource Source = new ImageSourceConverter().ConvertFromString(packUri) as ImageSource;
            return Source;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

2-bind your Image Source to parent's string properety (i used "tag" property)
like this xaml:

<Image HorizontalAlignment="Right"  Height="Auto" Margin="0,11.75,5.5,10.75" VerticalAlignment="Stretch" Width="40.997" Source="{Binding Path=Tag, RelativeSource={RelativeSource TemplatedParent}}"/>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文