I have a WPF application that needs to provide feedback to the user about an internal state. The design is to have three images, call them Red, Yellow, and Green. One of these images will be displayed at a time depending on the state. Here are the points:
The three images are in Properties.Resources in the code-behind
Only one of the images will be shown at a time.
The state change comes from a process in code-behind and not from the user.
I would like to bind an image control so that I can change the image from code-behind.
I’m assuming I’ll need an image converter to change the JPG image to an image source such as:
[ValueConversion(typeof(System.Drawing.Bitmap), typeof(ImageSource))]
public class BitmapToImageSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var bmp = value as System.Drawing.Bitmap;
if (bmp == null)
return null;
return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
bmp.GetHbitmap(),
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
I’d prefer to convert the images once during initialization and keep a list of Image sources. I’m also assuming I’ll need a dependency property to bind the control to, but I’m not sure how to set that up with this list of image sources:
// Dependancy Property for the North Image
public static readonly DependencyProperty NorthImagePathProperty
= DependencyProperty.Register(
"NorthImagePath",
typeof(ImageSource),
typeof(MainWindow),
new PropertyMetadata("**Don't know what goes here!!!**"));
// Property wrapper for the dependancy property
public ImageSource NorthImagePath
{
get { return (ImageSource)GetValue(NorthImagePathProperty); }
set { SetValue(NorthImagePathProperty, value); }
}
Although an image resource in a WPF project generates a System.Drawing.Bitmap property in Resources.Designer.cs, you could directly create a BitmapImage from that resource. You only need to set the Build Action of the image file to Resource (instead of the default None).
If you have a file Red.jpg in the Resources folder of your Visual Studio Project, creating a BitmapImage would look like shown below. It uses a WPF Pack Uri.
var uri = new Uri("pack://application:,,,/Resources/Red.jpg");
var bitmap = new BitmapImage(uri);
If you have an Image control declared somewhere in XAML like this:
<Image x:Name="image"/>
you could simply set the Source property of the image to your BitmapImage in code behind:
image.Source = bitmap;
In case you prefer to set the Source property by binding you could create a string property that returns the image URI. The string will automatically be converted to a BitmapImage by a built-in TypeConverter in WPF.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
ImageUri = "pack://application:,,,/Resources/Red.jpg";
}
public static readonly DependencyProperty ImageUriProperty =
DependencyProperty.Register("ImageUri", typeof(string), typeof(MainWindow));
public string ImageUri
{
get { return (string)GetValue(ImageUriProperty); }
set { SetValue(ImageUriProperty, value); }
}
}
In XAML you would bind to that property like this:
<Image Source="{Binding ImageUri}"/>
Of course you could as well declare the property to be of type ImageSource
public static readonly DependencyProperty ImageProperty =
DependencyProperty.Register("Image", typeof(ImageSource), typeof(MainWindow));
public ImageSource Image
{
get { return (ImageSource)GetValue(ImageProperty); }
set { SetValue(ImageProperty, value); }
}
and bind in the same way:
<Image Source="{Binding Image}"/>
Now you could pre-load your images and put them into the property as needed:
private ImageSource imageRed =
new BitmapImage(new Uri("pack://application:,,,/Resources/Red.jpg"));
private ImageSource imageBlue =
new BitmapImage(new Uri("pack://application:,,,/Resources/Blue.jpg"));
...
Image = imageBlue;
UPDATE: After all, your images do not need to be resources in the Visual Studio project. You could just add a project folder, put the image files into that folder and set their Build Action to Resource. If for example you call the folder Images, the URI would be pack://application:,,,/Images/Red.jpg.
Related
So I'm trying to do binding with a list of bitmapImage using async-await, but for some reason I don't see the images on the UI.
In the loop where I'm trying to update the property, when I do:
ImageSource = imageList[2];
or any other number between 0 to 9 then the image is shown in the UI screen.
here is part of my code until the loop where the binding doesn't happend:
EDIT: this was the problam:
private BitmapImage imageSource = null;
public BitmapImage ImageSource
{
get
{
return imageSource;
}
set
{
imageSource = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("MessagePerSec");
}
}
I see one problem straight off.
You have the wrong string:
public BitmapImage **ImageSource**
{
get
{
return imageSource;
}
set
{
imageSource = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("**MessagePerSec")**;
}
}
MessagePerSec instead of ImageSource. Which is itself a bad name by the way.
I am using SharpVector's SvgViewBox to show static resource images like this:
<svgc:SvgViewbox Source="/Resources/label.svg"/>
which works fine. However, I wish to control what image is shown through a binding to a view model.
The problem I'm experiencing is that the Source property of SvgViewbox is not bindable.
How can I get around this limitation without violating MVVM (e.g., passing the control into the view model and modifying it there)?
What you are looking for is called attached properties. MSDN offers a topic on it with the title "Custom Attached Properties"
In your case it may look as simple as this
namespace MyProject.Extensions
{
public class SvgViewboxAttachedProperties : DependencyObject
{
public static string GetSource(DependencyObject obj)
{
return (string) obj.GetValue(SourceProperty);
}
public static void SetSource(DependencyObject obj, string value)
{
obj.SetValue(SourceProperty, value);
}
private static void OnSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var svgControl = obj as SvgViewbox;
if (svgControl != null)
{
var path = (string)e.NewValue;
svgControl.Source = string.IsNullOrWhiteSpace(path) ? default(Uri) : new Uri(path);
}
}
public static readonly DependencyProperty SourceProperty =
DependencyProperty.RegisterAttached("Source",
typeof (string), typeof (SvgViewboxAttachedProperties),
// default value: null
new PropertyMetadata(null, OnSourceChanged));
}
}
XAML to use it
<SvgViewbox Margin="0 200"
local:SvgViewboxAttachedProperties.Source="{Binding Path=ImagePath}" />
Note that local is the namespace prefix and it should point to your assembly/namespace where that class is located at, i.e. xmlns:local="clr-namespace:MyProject.Extensions;assembly=MyProject".
Then only use your attached property (local:Source) and never the Source property.
The new attached property local:Source is of type System.Uri. To update the image first assign null then the filename/filepath again.
I have a UserControl with a property called BackImage:
public Metafile BackImage { get; set; }
I then draw this image as a background on the UserControl in the Paint event with this code:
if (BackImage != null)
e.Graphics.DrawImage(BackImage, this.ClientRectangle);
I use a .wmf image as I need the vector format when resizing the UserControl.
When dragging the UserControl to a form at design time and setting this property via the properties window in Visual Studio the image is correctly shown when repainted.
The problem is that when running the program Visual Studio throws an "InvalidCastException" error in the designer file of the containing form. This is the line with the error:
this.imageControl1.BackImage = ((System.Drawing.Imaging.Metafile)(resources.GetObject("imageControl1.BackImage")));
This is a well known bug in Visual Studio:
http://www.tek-tips.com/viewthread.cfm?qid=425541
As the image must also be shown at design time it is no option to just load it at runtime.
Question:
Instead of the property being of type Metafile is it possible to use a kind of raw format, and then just convert or typecast it when using it in the code?
This problem is caused by the lack of support in GDI+ for encoding Emf/Wmf metafiles. It can only read them. The built-in image editor that you get when you select the metafile converts its to a PNG image to bypass the restriction. You'll see it displayed in the designer, but it is not actually a metafile. This PNG image is also what gets stored in the .resx file. Kaboom when you then run your program, that PNG cannot be converted into a Metafile.
So a workaround is not make the property a Metafile, that just can't work. I had quite a bit of trouble finding an alternative, the obvious choice of byte[] as the property type made the code serializer hang when trying to find a type converter. Very strange, no idea why. I settled on a List<byte> instead:
private List<byte> BackImageBytes;
[Editor(typeof(MyMetafileEditor), typeof(UITypeEditor))]
public List<byte> BackImage {
get { return BackImageBytes; }
set {
BackImageBytes = value;
if (value == null) base.BackgroundImage = null;
else base.BackgroundImage = new Metafile(new System.IO.MemoryStream(value.ToArray()));
}
}
You'll need to override the BackgroundImage property so it doesn't get serialized:
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public override Image BackgroundImage {
get { return base.BackgroundImage; }
set { base.BackgroundImage = value; }
}
The Reset command in the context menu for the property is disabled, you need to add this go get it back:
private void ResetBackImage() {
BackImage = null;
}
And finally you need a replacement for the default property editor, it needs to load a metafile and convert it to a list:
class MyMetafileEditor : UITypeEditor {
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) {
using (var dlg = new OpenFileDialog()) {
dlg.Filter = "Metafiles (*.wmf, *.emf)|*.wmf;*.emf";
if (dlg.ShowDialog() == DialogResult.OK) {
value = new List<byte>(System.IO.File.ReadAllBytes(dlg.FileName));
}
}
return value;
}
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) {
return UITypeEditorEditStyle.Modal;
}
}
This is the first time I have used Silverlight and as such, I am new to the whole xaml markup style. I am building a GIS application using a silverlight library provided by ESRI.
From my understanding, when the XAML page is parsed from top to bottom, the objects are created in the order. Is this correct? I have an esri Map object created on line 38 of my mainpage and then on line 247 of my mainpage, I create a DrawControl (a drawing control I made myself).
Part of how the objects in drawing tool works is that it is created by passing the map object to the constructor. With my Map object with name x:Name="Map", I have the following for my drawcontrol:
<local:DrawRootControl x:Name="DrawRoot" Height="152" Margin="216,10,0,0" Grid.Row="1" VerticalAlignment="Top" Visibility="Collapsed" map="{Binding ElementName=Map}"/>
Then in my control, I have this in the code behind:
public static readonly DependencyProperty mapProperty = DependencyProperty.Register
(
"map",
typeof(Map),
typeof(DrawRootControl),
null
);
public Map map
{
get { return (Map)GetValue(mapProperty); }
set { SetValue(mapProperty, value); }
}
..........
public DrawRootControl()
{
// Required to initialize variables
InitializeComponent();
MyDrawObject = new Draw(map)
{
LineSymbol = CanvasDraw.Resources["DrawLineSymbol"] as LineSymbol,
FillSymbol = CanvasDraw.Resources["DrawFillSymbol"] as FillSymbol
};
MyDrawObject.DrawComplete += MyDrawObject_DrawComplete;
}
When I am debugging, my map object in my constructor is null. I thought that if map is created earlier in the mainpage and then passed when I do that binding, it would not be null and would be initialized and created. Maybe I am doing binding incorrectly? I don't really understand the binding thing entirely.
Any help would be appreciated.
From my understanding, when the XAML page is parsed from top to bottom, the objects are created in the order. Is this correct?
yes top to bottom like html. Example:
<Grid x:Name="LayoutRoot" Background="White">
<Rectangle Fill="#FFE53400" Height="132" />
<Rectangle Fill="#FF0000E5" Height="132" Margin="0,51,0,0" />
</Grid>
Part of how the objects in drawing tool works is that it is created by passing the map object to the constructor.
If you are dependent to another UI element you will need to implement the callback to draw your control when the DependencyProperty has changed. In this example replace Title with Map
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(ComparisonReport), new PropertyMetadata(null, OnTitleChanged));
private static void OnTitleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var o = d as ComparisonReport;
if (o != null && e.NewValue != null)
{
var n = ((ComparisonReport)d);
n.RadChart1.DefaultView.ChartArea.AxisX.Title = String.Format("{0} Comparison", e.NewValue);
}
}
If you have written a custom control you can wait till OnApplyTemplate() at which point you can locate the part (esri map object) by name. GetTemplateChild you can then attach to the esri events that affect your custom drawing.
I want to set an image's source according to its DataContext in a ChildWindow. Here is the XAML file:
<controls:ChildWindow x:Class="CEM.Controls.DialogWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls" Title="{Binding Title}">
...
<Image x:Name="DialogIcon"></Image>
...
</controls:ChildWindow>
It's working fine if I override the Show method of the ChildWindow and set the image's source:
public new void Show()
{
DialogIcon.Source = new BitmapImage(new Uri(#"/Images/DialogWindow/Confirm.png", UriKind.Relative));
base.Show();
}
But it looks ugly and it's not the "silverlight way", so I decide to change:
<Image x:Name="DialogIcon" Source="{Binding DialogIconType, Converter={StaticResource DialogIconConverter}}"></Image>
You see I have a DialogIconConverter registered to bind the source from the DataContext.
public class DialogIconConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//currently it's an hard-coded path
return new BitmapImage(new Uri(#"/Images/DialogWindow/Confirm.png", UriKind.Relative));
}
...
}
But it's not working now, I have several other converters in this control which are working fine. Only this one is not working. Can you help to find where the problem is?
EDIT: DialogIconType is an enum, and also it's a property of DialogContext. An instance of DialogContext will be assigned to DataContext property of the DialogWindow.
public enum DialogIconType
{
Confirm,
Alert,
Error
}
public class DialogContext
{
public string Title { get; set; }
public string Content { get; set; }
public DialogButtons Buttons { get; set; }
public DialogIconType IconType { get; set; }
}
internal DialogWindow(DialogContext context)
{
InitializeComponent();
this.DataContext = context;
}
might be silly, but did you make sure that your converter is referenced properly in your xaml file ?
otherwise, I suggest trying this syntax as path for your URI (with images setup as resources):
return new BitmapImage(new Uri("pack://application:,,,/Images/DialogWindow/Confirm.png", UriKind.Relative));
EDIT :
ok, I think I've got it :
look into your output window, you will probably see some error 40 binding ... blablabla...
My guess is that the converter is right, but the source of the binding isn't, so basically the converter is not even used.
The reason is that your DialogIconType is not a dependency property, so it cannot be bound to.
in other words, this :
public DialogIconType IconType { get; set; }
should become this :
public static DependencyProperty IconTypeProperty = DependencyProperty.Register("IconType", typeof(DialogIconType), typeof(DialogContext));
public DialogIconType IconType
{
get { return (DialogIconType)(GetValue(IconTypeProperty)); }
set { SetValue(IconTypeProperty , value); }
}
plus, in your Xaml, you should Bind to "IconType", and not "DialogIconType" (which is a type and not a property)
(this might even be the sole issue, as I'm not sure if a dependencyProperty Is actually realy needed here, now that I think of it)
Assuming that DialogIconType is the path to your image (e.g. "Images/DialogWindow/Confirm.png"), it should work without a valueconverter as shown below:
<Image Source="{Binding DialogIconType}" />
EDIT:
Returning the path to the image from the valueconverter's Convert method is also possible - i.e.:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return "Images/DialogWindow/Confirm.png";
}
EDIT 2:
The following also works using UriKind.Relative:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new BitmapImage(new Uri("Images/DialogWindow/Confirm.png", UriKind.Relative));
}