自制的ImageButton嵌入到一个UserControl中,嵌入到另一个UserControl中,不显示图像
我编写了一个基本的 ImageButton 控件,它源自 Button。下面是 Generic.XAML 中按钮样式的 XAML:
<Style TargetType="{x:Type local:ImageButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ImageButton}">
<Button Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Clip="{TemplateBinding Clip}"
ClipToBounds="{TemplateBinding ClipToBounds}"
FlowDirection="{TemplateBinding FlowDirection}"
Height="{TemplateBinding Height}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Margin}"
MaxHeight="{TemplateBinding MaxHeight}"
MaxWidth="{TemplateBinding MaxWidth}"
MinHeight="{TemplateBinding MinHeight}"
MinWidth="{TemplateBinding MinWidth}"
Opacity="{TemplateBinding Opacity}"
OpacityMask="{TemplateBinding OpacityMask}"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
ToolTip="{TemplateBinding ToolTip}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Visibility="{TemplateBinding Visibility}"
Width="{TemplateBinding Width}" >
<Image Name="Image"
HorizontalAlignment="Stretch"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Source="{TemplateBinding Source}"
Stretch="Uniform"
VerticalAlignment="Stretch" />
</Button>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
如您所见,模板由一个带有 Image 控件的按钮组成。该类的隐藏代码如下所示:
public partial class ImageButton : Button {
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register( "Source", typeof( ImageSource ), typeof( ImageButton ),
new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsParentMeasure |
FrameworkPropertyMetadataOptions.AffectsRender ) );
public ImageSource Source {
get { return (ImageSource) GetValue( SourceProperty ); }
set { SetValue( SourceProperty, value ); }
}
public ImageButton() : base() {}
static ImageButton() {
// Tell this control to use our default style property in Generic.xaml
DefaultStyleKeyProperty.OverrideMetadata( typeof( ImageButton ), new FrameworkPropertyMetadata( typeof( ImageButton ) ) );
}
}
我知道这个控件的工作原理,因为我编写了一个测试程序,该程序从硬盘驱动器加载 JPEG,从文件流创建 BitmapImage 对象,并将 ImageButton 的 Source 属性设置为新的 BitmapImage 对象。图像显示 &用户可以点击它。
我有一个 UserControl,其中嵌入了 ImageButton 的实例。下面是该控件的 XAML。
<UserControl x:Class="CarSystem.CustomControls.Channel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:cs="clr-namespace:CarSystem.CustomControls"
mc:Ignorable="d"
d:DesignHeight="211" d:DesignWidth="281">
<Grid>
<cs:ImageButton BorderBrush="Black"
BorderThickness="1"
Click="CarImage_Click"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
x:Name="CarImage"
VerticalAlignment="Stretch"
VerticalContentAlignment="Stretch" />
<Canvas Name="ChannelCanvas">
<ComboBox Background="{x:Null}"
FontSize="18"
Foreground="Black"
HorizontalContentAlignment="Center"
Margin="5"
MinHeight="25"
Name="CameraPicker"
Panel.ZIndex="1"
SelectionChanged="Picker_SelectionChanged"
VerticalAlignment="Top"
Visibility="Hidden" />
<Rectangle Fill="{x:Null}"
Margin="5"
MinHeight="25"
Name="NameRectangle"
RadiusX="2"
RadiusY="2"
Stroke="Black"
Visibility="Hidden" />
<TextBlock FontSize="18"
Foreground="Black"
MinHeight="25"
Name="CameraName"
Visibility="Hidden" />
</Canvas>
</Grid>
Channel 的代码隐藏非常长且复杂,所以我无法包含所有内容。 Channel 有一个名为 DisplayRead 的方法,它采用一个名为“read”的 DisplayRead 类型(不同命名空间)的对象,该对象是从数据库读取的。该对象包含两个 JPEG,存储为字节数组。 DisplayRead 中的代码决定在 ImageButton 中显示两个 JPEG 中的哪一个,然后执行以下代码来执行此操作:
if ( read.OverviewImage != null ) {
OverviewImage = new BitmapImage();
using ( MemoryStream memoryStream = new MemoryStream( read.OverviewImage.ImageBytes ) ) {
OverviewImage.BeginInit();
OverviewImage.StreamSource = memoryStream;
OverviewImage.EndInit();
}
}
DisplayImage 中的代码仅将要显示的数据(如上面的图像)存储在 CLR 属性中。存储这些值后,它使用 Channel 的 Dispatcher 对象来调度 UpdateChanel 方法运行,该方法实际上设置属性以显示存储在 CLR 属性中的数据。以下是 UpdateChannel 的摘录:
// CarImage is an instance of ImageButton in the Channel control
if ( CarImage != null && OverviewImage != null ) {
CarImage.Source = image;
}
// PlateCloseupImageButton is an instance of ImageButton on the UserControl that is this Channel's parent.
// PlateCloseupImage is another CLR property. It holds a BitmapSource that contains a rectangle taken from
// another JPEG in the Read.
PlateCloseupImageButton.Source = PlateCloseupImage;
从上面的代码片段来看,放入 CarImage 的 Source 属性中的图像未显示,但放入 PlateCloseupImageButton 中的图像确实显示。
我使用 SNOOP 遍历可视化树,显示两个 ImageButton 的 Source 属性均已设置,但 CarImage 嵌入图像的 source 属性未设置,但 PlateCloseupImageButton 的 Source 已设置。不过,我向 ImageButton 的 Source 属性中的 setter 添加了一些代码,以查看 Image 的 source 属性是否确实为 null。事实并非如此;它的设置正是我所期望的。
这让我很困惑。我不知道为什么嵌入在通道中的 ImageButton 不显示其图像。我发现的唯一区别是:
CarImage 嵌入在视觉树的更下方。应该没有什么区别。
PlateImage 的 Source 类型为 BitmapImage,PlateCloseupImageButton 的 Source 类型为 CachedBitmap。
任何帮助将不胜感激。
托尼
I've written a basic ImageButton control, which descends from Button. Here's the XAML for the button's style in Generic.XAML:
<Style TargetType="{x:Type local:ImageButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ImageButton}">
<Button Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Clip="{TemplateBinding Clip}"
ClipToBounds="{TemplateBinding ClipToBounds}"
FlowDirection="{TemplateBinding FlowDirection}"
Height="{TemplateBinding Height}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Margin}"
MaxHeight="{TemplateBinding MaxHeight}"
MaxWidth="{TemplateBinding MaxWidth}"
MinHeight="{TemplateBinding MinHeight}"
MinWidth="{TemplateBinding MinWidth}"
Opacity="{TemplateBinding Opacity}"
OpacityMask="{TemplateBinding OpacityMask}"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
ToolTip="{TemplateBinding ToolTip}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Visibility="{TemplateBinding Visibility}"
Width="{TemplateBinding Width}" >
<Image Name="Image"
HorizontalAlignment="Stretch"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Source="{TemplateBinding Source}"
Stretch="Uniform"
VerticalAlignment="Stretch" />
</Button>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
As you can see, the Template consists of a button with an Image control in it. Here's what the code-behind for the class looks like:
public partial class ImageButton : Button {
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register( "Source", typeof( ImageSource ), typeof( ImageButton ),
new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsParentMeasure |
FrameworkPropertyMetadataOptions.AffectsRender ) );
public ImageSource Source {
get { return (ImageSource) GetValue( SourceProperty ); }
set { SetValue( SourceProperty, value ); }
}
public ImageButton() : base() {}
static ImageButton() {
// Tell this control to use our default style property in Generic.xaml
DefaultStyleKeyProperty.OverrideMetadata( typeof( ImageButton ), new FrameworkPropertyMetadata( typeof( ImageButton ) ) );
}
}
I know this control works as I've written a test program that loads a JPEG from the hard drive, creates a BitmapImage object from the file stream, and sets the ImageButton's Source property to the new BitmapImage object. The image displays & the user can click on it.
I have a UserControl in which I've embedded an instance of the ImageButton. Here's the XAML for that control.
<UserControl x:Class="CarSystem.CustomControls.Channel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:cs="clr-namespace:CarSystem.CustomControls"
mc:Ignorable="d"
d:DesignHeight="211" d:DesignWidth="281">
<Grid>
<cs:ImageButton BorderBrush="Black"
BorderThickness="1"
Click="CarImage_Click"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
x:Name="CarImage"
VerticalAlignment="Stretch"
VerticalContentAlignment="Stretch" />
<Canvas Name="ChannelCanvas">
<ComboBox Background="{x:Null}"
FontSize="18"
Foreground="Black"
HorizontalContentAlignment="Center"
Margin="5"
MinHeight="25"
Name="CameraPicker"
Panel.ZIndex="1"
SelectionChanged="Picker_SelectionChanged"
VerticalAlignment="Top"
Visibility="Hidden" />
<Rectangle Fill="{x:Null}"
Margin="5"
MinHeight="25"
Name="NameRectangle"
RadiusX="2"
RadiusY="2"
Stroke="Black"
Visibility="Hidden" />
<TextBlock FontSize="18"
Foreground="Black"
MinHeight="25"
Name="CameraName"
Visibility="Hidden" />
</Canvas>
</Grid>
The code-behind for Channel is very long and convoluted, so I can't include all of it. Channel has a method called DisplayRead which takes an object called "read" of type DisplayRead (different namespaces), which is read from a database. This object contains two JPEGs, stored as byte arrays. The code in DisplayRead decides which of the two JPEGs to display in the ImageButton and then executes the following code to do so:
if ( read.OverviewImage != null ) {
OverviewImage = new BitmapImage();
using ( MemoryStream memoryStream = new MemoryStream( read.OverviewImage.ImageBytes ) ) {
OverviewImage.BeginInit();
OverviewImage.StreamSource = memoryStream;
OverviewImage.EndInit();
}
}
The code in DisplayImage just stores data to be displayed, like the images above, in CLR properties. After storing these values, it uses the Channel's Dispatcher object to schedule the UpdateChanel method to run, which actually sets the properties to display the data stored in the CLR properties. Here's an excerpt from UpdateChannel:
// CarImage is an instance of ImageButton in the Channel control
if ( CarImage != null && OverviewImage != null ) {
CarImage.Source = image;
}
// PlateCloseupImageButton is an instance of ImageButton on the UserControl that is this Channel's parent.
// PlateCloseupImage is another CLR property. It holds a BitmapSource that contains a rectangle taken from
// another JPEG in the Read.
PlateCloseupImageButton.Source = PlateCloseupImage;
From the above snippet, the image put into CarImage's Source property is not displaying, but the one put into PlateCloseupImageButton does display.
I've used SNOOP to walk the visual tree and that shows the Source Property of both ImageButtons are set, but the source property of CarImage's embedded Image is not set, but the PlateCloseupImageButton's Source is set. However, I added some code to the setter in the ImageButton's Source property to see if the Image's source property really was null. It wasn't; it was set to exactly what I expected it to be set to.
This has me baffled. I have no idea why the ImageButton embedded in the Channel isn't displaying its image. The only differences I've been able to find are that:
CarImage is embedded further down the Visual Tree. Shouldn't make a difference.
The type of the Source for PlateImage is BitmapImage, while that of PlateCloseupImageButton is CachedBitmap.
Any help would be greatly appreciated.
Tony
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
好的,在团队领导的帮助下,我找到了这个问题的答案。问题的根源在下面的代码中:
using语句是问题的原因。 using 语句在 BitmapImage 从中读取图像之前关闭了 MemoryStream。结果是,在显示 BitmapImage 之前,不会从流中读取它的字节。当我删除 using 语句时,一切正常。
托尼
OK, I found the answer to this one, with the help of my team lead. The source of the problem is in the following code:
The using statement is the cause of the problem. The using statement closed the MemoryStream before the BitmapImage could read the image from it. Turns out the bytes for the BitmapImage aren't read from the stream until it is displayed. When I removed the using statement, everything works.
Tony