WPF中Image控件的绑定
背景
在我们平时的开发中会经常用到Image控件,通过设置Image控件的Source属性,我们可以加载图片,设置Image的source属性时可以使用相对路径也可以使用绝对路径,一般情况下建议使用绝对路径,类似于下面的形式
Source="/Demo;Component/Images/Test.jpg"
其中Demo表示工程的名称,后面表示具体哪个文件夹下面的哪个图片资源,在程序中,我们甚至可以为Image控件设置 x:Name 属性,在后台代码中动态去改变Image的Source,但我个人认为这种方式不太适合最大量的图片切换,而且增加了View层和代码之间的耦合性,不是和复合MVVM的核心设计思想,所以今天就总结一下Image的动态绑定的形式。
过程
1 常规操作
要绑定肯定是绑定到Image控件的Source属性上面,我们首先要搞清楚 Source 的类型是什么?
public ImageSource Source { get; set; }
也就是ImageSource类型,当然在我们绑定的时候用的最多的就是BitmapImage这个位图图像啦,我们首先来看看BitmapImage的继承关系:BitmapImage:BitmapSource:ImageSource,最终也是一种ImageSource类型。当然在我们的Model层中我们也可以直接定义一个BitmapImage的属性,然后将这个属性直接绑定到Image的Source上面,当然这篇文章我们定义了一个ImgSource的String类型,所以必须要定义一个转换器Converter,这里分别贴出相应地代码。
1.1 定义View
<Grid Grid.Row="1"> <Image Source="{Binding Path=LTEModel.ImgSource,Converter={StaticResource MyImageConverter}}" Stretch="Fill"> </Image> </Grid>
1.2 定义Model
public class LTEModel : BaseModel { private string _imageSource = null; public string ImgSource { get { return _imageSource; } set { if (value != _imageSource) { _imageSource = value; FirePropertyChanged("ImgSource"); } } } }
1.3 定义转换器
public class StringToImageSourceConverter:IValueConverter { #region Converter public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { string path = (string)value; if (!string.IsNullOrEmpty(path)) { return new BitmapImage(new Uri(path, UriKind.Absolute)); } else { return null; } } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return null; } #endregion }
转换器返回的是Object类型,实际返回的是一个BitmapImage对象。所以我们在写程序绑定的时候一定要弄清绑定的目标和对象之间的关系,这个是非常重要的。
1.4 定义ViewModel
下面就是在ViewModel层中来添加绑定,并更新数据源,这里使用的是一个定时器来定时更新数据源:
public class LTEViewModel : NotifyObject { private DispatcherTimer myDispatcher = null; private Random random = new Random(); public LTEViewModel() { GetImageSource(); InitTimer(); } private LTEModel _lteModel = null; public LTEModel LTEModel { get { if (_lteModel == null) { _lteModel = new LTEModel(); } return _lteModel; } set { if (value != _lteModel) { _lteModel = value; FirePropertyChanged("LTEModel"); } } } private BaseModel _baseModel = null; public BaseModel BaseModelInstance { get { if (_baseModel == null) { _baseModel = new BaseModel() { Title = "分地区LTE分布", Time = DateTime.Now.ToString() }; } return _baseModel; } set { if (value != _baseModel) { _baseModel = value; FirePropertyChanged("BaseModelInstance"); } } } private List<string> imgList = new List<string>(); private void GetImageSource() { //通过程序集来读取相应的资源的路径 string assemblyLocation = this.GetType().Assembly.Location; string assLocation = assemblyLocation.Substring(0, assemblyLocation.LastIndexOf("\\")); string[] img_files = Directory.GetFiles(string.Format("{0}\\Images", assLocation), "*.JPG"); foreach (string img_path in img_files) { imgList.Add(img_path); } } private void InitTimer() { myDispatcher = new DispatcherTimer(); myDispatcher.Tick += new EventHandler(Timer_Tick); myDispatcher.Interval = TimeSpan.FromMilliseconds(1000); myDispatcher.Start(); } private void Timer_Tick(object sender, EventArgs e) { int imageIndex = 0; if (imgList.Count > 0 && LTEModel != null) { imageIndex = random.Next(0, imgList.Count); LTEModel.ImgSource = imgList[imageIndex]; } if (_baseModel != null) { _baseModel.Time = DateTime.Now.ToString(); } } }
2 扩展应用
很多时候我们还有一种需求就是已经获取到了.bmp文件,那么我们该如何将该文件绑定到Image上面呢? 通过上面的分析我们大体可以将整个过程分为以下几个部分:1 获取本地文件并加载Bitmap。2 将Bitmap对象转换为BitmapImage对象。3 将BitmapImage绑定到Image的Source属性上面,下面就每一个过程来进行一个分析。
2.1 加载本地Bitmap文件
这个通过下面的一行代码就可以搞定,这里fullPath是本地的目录
var bitmapImage = new Bitmap(fullPath, true);
2.2 将Bitmap对象转换为BitmapImage对象
在考虑直接转换之前,我们来思考一种常用的情况,如果我们的代码是UI端和Server端分布在两台不同的电脑上面,我们的bmp图片在服务器上,这是我们需要将获取到的图片发送到客户端,这时肯定不会直接传图片,我们会选择将图片转换成字符串或者byte[]数组的形式,下面的示例表示将Bitmap转换成string,并在客户端再次将string转换成Bitmap的方式,看下面的代码。
public static class BitMapToBase64Helper { /** * 将bitmap转换成base64字符串 * @param bitmap * @return base64 字符串 */ public static string BitmapToString(Bitmap bitmap) { using (var memoryStream = new MemoryStream()) { bitmap.Save(memoryStream, ImageFormat.Jpeg); byte[] byteImage = memoryStream.ToArray(); // Get Base64 return Convert.ToBase64String(byteImage); } } /** * 将base64转换成bitmap图片 * @param string base64字符串 * @return bitmap */ public static Bitmap StringToBitmap(string base64String) { Bitmap bmpReturn = null; //Convert Base64 string to byte[] byte[] byteBuffer = Convert.FromBase64String(base64String); using (var memoryStream = new MemoryStream(byteBuffer)) { memoryStream.Position = 0; bmpReturn = (Bitmap)Bitmap.FromStream(memoryStream); return bmpReturn; } } }
有了这个我们就可以实现图片和字符串之间的相互转换了,在获取到Bitmap对象后我们需要通过下面的代码来将Bitmap转换成BitmapImage从而可以将其绑定到Image的Source属性上面。
private BitmapImage BitmapToImageSource(Bitmap bitmap) { using (var memory = new MemoryStream()) { //这里需要将原来的bitmap重新复制一份出来使用newBitmap进行Save 否则会报GDI+中出现一般性错误 //https://stackoverflow.com/questions/15862810/a-generic-error-occurred-in-gdi-in-bitmap-save-method Bitmap newBitmap = new Bitmap(bitmap); bitmap.Dispose(); bitmap = null; newBitmap.Save(memory, System.Drawing.Imaging.ImageFormat.Bmp); memory.Position = 0; BitmapImage bitmapImage = new BitmapImage(); bitmapImage.BeginInit(); bitmapImage.StreamSource = memory; bitmapImage.CacheOption = BitmapCacheOption.OnLoad; bitmapImage.EndInit(); return bitmapImage; } }
这里需要关注一点就是代码中加了中文注释的那个部分,就是通过string创建出来的Bitmap再将其转换为BitmapImage的时候需要重新创建一个原来Bitmap对象的一个副本,否则会出现 出现GDI+出现一般的错误的Exception,具体的解释可以参考文中的链接。
2.3 将BitmapImage绑定到Image的Source属性
这个就不再赘述了,直接将上面步骤中的BitmapImage绑定到Image的Source属性即可。
3 其它应用
这里说一个通过定义DeviceType属性,然后在Trigger中绑定不同图片的方式,我们来看看这个实现的过程。
其实在我们的很多时候,我们并不知道我们需要绑定什么图片,或者说根据数据类型来绑定图片,这个在定义数据模板的时候经常使用到,下面就介绍一下,根据类型来绑定相应的图片,然后通过定义
public enum DeviceType { SheXiangJi, KaKou, DianZiJingCha, MingJin }
这种类型,通过不同的类型来绑定到不同的图片,这个也是一个非常重要的应用,我们一定要注意使用的方法,这里只是简单介绍一下。
<ItemsControl ItemsSource="{Binding DeviceList,RelativeSource={RelativeSource TemplatedParent}}" Grid.Row="2"> <ItemsControl.Template> <ControlTemplate TargetType="ItemsControl"> <UniformGrid Columns="3" Rows="7" IsItemsHost="True"></UniformGrid> </ControlTemplate> </ItemsControl.Template> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="20 0 0 0" VerticalAlignment="Center" SnapsToDevicePixels="True"> <Image x:Name="icon1" Width="48" Height="48" RenderOptions.BitmapScalingMode="NearestNeighbor" VerticalAlignment="Center"></Image> <TextBlock Margin="10 0 0 0" Foreground="#fff" ToolTip="{Binding Name}" FontSize="40" Text="{Binding Name}" HorizontalAlignment="Left" VerticalAlignment="Center"></TextBlock> </StackPanel> <DataTemplate.Triggers> <DataTrigger Binding="{Binding Type}" Value="SheXiangJi"> <Setter Property="Source" Value="/IGisControls.JTJ.UIControls;component/images/camera.png" TargetName="icon1"></Setter> </DataTrigger> <DataTrigger Binding="{Binding Type}" Value="KaKou"> <Setter Property="Source" Value="/IGisControls.JTJ.UIControls;component/images/Bayonet.png" TargetName="icon1"></Setter> </DataTrigger> <DataTrigger Binding="{Binding Type}" Value="DianZiJingCha"> <Setter Property="Source" Value="/IGisControls.JTJ.UIControls;component/images/epolice.png" TargetName="icon1"></Setter> </DataTrigger> <DataTrigger Binding="{Binding Type}" Value="MingJin"> <Setter Property="Source" Value="/IGisControls.JTJ.UIControls;component/images/Police_A.png" TargetName="icon1"></Setter> </DataTrigger> </DataTemplate.Triggers> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl>
另外和Image很类似的就是ImageBrush控件的使用,具体的使用方法如下代码所示。
<ImageBrush ImageSource="/IGisControls.JTJ.UIControls;component/images/screenBG.jpg" Stretch="Fill"></ImageBrush>
用法也差不多,同样可以通过绑定的方式来添加图片,不过在使用的时候还是需要注意一下就是设置当前图片的生成操作为Resource。