I am in the process of taking a Silverlight 4 UserControl containing a canvas which has a number of FrameworkElements on it and converting this to use databinding.
The XAML for my original canvas was:
<Canvas x:Name="panelDisplay" >
<Rectangle Width="50" Height="50" MouseLeftButtonDown="Element_MouseLeftButtonDown" Stroke="Aqua" StrokeThickness="5" Fill="Aquamarine" Canvas.Left="450" Canvas.Top="50" x:Name="rect1" />
<Image Source="../Images/3.jpg" Stretch="UniformToFill" Width="356" Height="224" MouseLeftButtonDown="Element_MouseLeftButtonDown" Canvas.Left="317" Canvas.Top="140" x:Name="image1" />
</Canvas>
This displays the rectangle and image and the MouseLeftButtonDown event fires which then deals with operations such as dragging and resizing.
In order to get this working with databinding I created an object called CanvasElement:
public class CanvasElement
{
public CanvasElement(int id, object elementContent, double width, double height, int left, int top)
{
Id = id;
ElementContent = elementContent;
Width = width;
Height = height;
Left = left;
Top = top;
}
public int Id { get; set; }
public object ElementContent { get; set; }
public double Width { get; set; }
public double Height { get; set; }
public int Left { get; set; }
public int Top { get; set; }
}
The ElementContent is used to store the Rectangle or Image. I populate an ObservableCollection called CanvasElements and assign the DataContext of the control. I have changed my XAML to:
<Canvas x:Name="panelDisplay" >
<ItemsControl x:Name="CanvasElements" ItemsSource="{Binding Path=CanvasElements}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<ContentControl Content="{Binding Path=ElementContent}" Height="{Binding Path=Height}" Width="{Binding Path=Width}"
Canvas.Left="{Binding Path=Left}" Canvas.Top="{Binding Path=Top}"
MouseLeftButtonDown="CanvasElement_MouseLeftButtonDown" />
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
My elements appear but the CanvasElement_MouseLeftButtonDown does not fire. I have also used ContentControl within the ItemsControl.
My questions are:
Is this is sensible way to go?
Why is MouseLeftButtonDown not firing?
You are better off to put the MouseLeftButtonDown event handler on the ItemsControl. Any events that happen on it's children will bubble up to it and you can handle them there.
I eventually decided that using the ItemsControl to bind to a collection of shapes was not the best way forward.
The main reason for this is that by using a control in the DataTemplate to house shapes adds extra complexity, in my case I have a main canvas and each shape was housed in its own canvas placed on the main canvas.
I believe this was causing the problem with the events and makes it more complex to position shapes.
Related
I am trying to "throw" several objects on a canvas.
I have 5 types of objects with several properties (like text, position, bitmap, and more)
Each type should be rendered differently (one type will be rendered as textblock, one as bitmapImage, ect.)
I have 5 observableCollections that holds all the objects of same type.
I can bind one of them (the one that represents text for example) to the canvas and using a data template with a textblock to bind each property to the right parameter (like visibility and localtion).
now my 2nd type should be bind to a bitmap.
How can I do that ? How can I bind 5 different types to the canvas and have each type converted to the right element ?
One possible way is to aggregate all collections to a single one... but then it tries to convert everything to the first type...
There`s a great answer to somehow related question that we can extend to make it work with your problem.
Say we have two types that can be dropped to Canvas:
public class TextClass
{
public string Text { get; set; }
}
public class RectangleClass
{
public Brush FillBrush { get; set; }
}
To facilitate the use of collection to bind to we can use the code from answer I mentioned but change ItemTemplate for our custom DataTemplateSelector:
<ItemsControl Name="icMain">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Left}" />
<Setter Property="Canvas.Top" Value="{Binding Top}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplateSelector>
<TestWPF:CustomTemplateSelector>
<TestWPF:CustomTemplateSelector.TextTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}" />
</DataTemplate>
</TestWPF:CustomTemplateSelector.TextTemplate>
<TestWPF:CustomTemplateSelector.RectangleTemplate>
<DataTemplate>
<Rectangle Height="25" Width="25" Fill="{Binding FillBrush}" />
</DataTemplate>
</TestWPF:CustomTemplateSelector.RectangleTemplate>
</TestWPF:CustomTemplateSelector>
</ItemsControl.ItemTemplateSelector>
</ItemsControl>
And that`s the template selector I used:
public class CustomTemplateSelector: DataTemplateSelector
{
public DataTemplate TextTemplate { get; set; }
public DataTemplate RectangleTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is TextClass)
return TextTemplate;
else if (item is RectangleClass)
return RectangleTemplate;
else return base.SelectTemplate(item, container);
}
}
Well, all that`s left is to bind our collection. I used simple List in code behind just for test:
List<object> aggregation = new List<object>()
{
new TextClass() { Text = "Some test text" },
new RectangleClass() { FillBrush = new SolidColorBrush(Colors.Tomato)}
};
icMain.ItemsSource = aggregation;
This code shows some test text and yummy tomato rectangle. These sample objects do not have any positioning logic, but I figured you have that already.
How to simulate a tile view for a ListView in WPF?
I was trying the example shown here. But I can't get to the right solution... But I don't want to use that solution as I it's too much specific. So how will be the way to accomplilsh that?
EDIT: I'm trying this now and seems to work...
<ListBox ItemsSource="{Binding Path=ListObservableUsers, ElementName=AdminWindow}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<Image Source="{Binding Path=Picture}"></Image>
<Label Content="{Binding Path=Dni}"></Label>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Where the ElementName=AdminWindow comes from <Window .... x:Name="AdminWindow"
And I created my own ObservableCollection<MyUser>
public class MyUser
{
public MyUser(int id, string dni, Bitmap picture)
{
Id = id;
Dni = dni;
Image img = new Image();
FPhiMultipleSources.FromBitmapImage(img, picture);
Picture = img.Source;
}
public int Id { get; set; }
public string Dni { get; set; }
public ImageSource Picture { get; set; }
}
...
public UCAdminMain()
public UCAdminMain()
{
ListObservableUsers = new ObservableCollection<MyUser>();
InitializeComponent();
uiCurrent = SynchronizationContext.Current;
// Create users to add with its image
....
ListObservableUsers.Add(...);
}
And now I'm trying to put them inside a wrap panel. With no luck right now... Any ideas?
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
try to use a wrappanel
An ItemsControl with a WrapPanel as the ItemsContainer would probably be a good fit for what you are trying to do.
Is it possible to use a DataTemplate to render a collection of points as a bunch of lines (with data binding and drag and drop)?
Here are the details:
I have multiple objects in my view model. These objects ultimately have locations on a canvas specified in absolute pixel coordinates. I need to be able to drag and drop these items around on the canvas and update their coordinates. Some objects are represented by a point, others are a collection of line segments. I'm using MVVM (Jounce). Should my view model expose a ObservableCollection<Shape> that somehow binds the coordinates? That feels wrong. Or is there a way I can use DataTemplates here to draw lines with points on the end of each line segment given a collection of line segments? Here is an example ViewModel:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Jounce.Core.ViewModel;
namespace CanvasBindTest.ViewModels
{
/// <summary>
/// Sample view model showing design-time resolution of data
/// </summary>
[ExportAsViewModel(typeof(MainViewModel))]
public class MainViewModel : BaseViewModel
{
public MainViewModel()
{
var start = new PointView { X = 0, Y = 0 };
var middle = new PointView { X = 1132 / 2, Y = 747 / 2 };
var end = new PointView() { X = 1132, Y = 747 };
var lineView = new LineView(new[] { start, middle, end });
Lines = new LinesView(new[] { lineView });
}
public LinesView Lines { get; private set; }
}
public class LinesView : BaseViewModel
{
public ObservableCollection<LineView> Lines { get; private set; }
public LinesView(IEnumerable<LineView> lines)
{
Lines = new ObservableCollection<LineView>(lines);
}
}
public class LineView : BaseViewModel
{
public ObservableCollection<PointView> Points { get; private set; }
public LineView(IEnumerable<PointView> points)
{
Points = new ObservableCollection<PointView>(points);
}
}
public class PointView : BaseViewModel
{
private int x, y;
public int X
{
get { return x; }
set { x = value; RaisePropertyChanged(() => X); }
}
public int Y {
get { return y; }
set { y = value; RaisePropertyChanged(() => Y); }
}
}
}
Here is the View, which is a canvas wrapped in a ItemsControl with a background image. The view model coordinates are relative to the background image's unscaled size:
<UserControl x:Class="CanvasBindTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:CanvasBindTest.ViewModels"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<DataTemplate x:Key="SkylineTemplate" DataType="viewModels:LineView">
<ItemsControl ItemsSource="{Binding Points}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!--I have a collection of points here, how can I draw all the lines I need and keep the end-points of each line editable?-->
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</UserControl.Resources>
<Grid d:DataContext="{d:DesignInstance viewModels:MainViewModel, IsDesignTimeCreatable=True}">
<ScrollViewer x:Name="Scroll">
<ItemsControl ItemsSource="{Binding Lines}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas>
<Canvas.Background>
<ImageBrush Stretch="Uniform" ImageSource="Properties/dv629047.jpg"/>
</Canvas.Background>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</Grid>
</UserControl>
Your LineView must be LineViewModel, it'll be more correct.
I describe the mechanism for points, for lines I think you will understand by yourself.
Main Control
Use ItemsControl.
ItemsControl.PanelControl must be Canvas.
ItemsSource - Your collection of PointWiewModel.
Make two DataTemplates for types PointWiewModel.
Make the PointView control and put it into the appropriate DataTemplate.
PointView control
Two way bind Canvas.X attached property to PointViewModel.X property.
Two way bind Canvas.Y attached property to PointViewModel.Y property.
Add logic of changing Canvas.X and Canvas.Y when you drag a PointView control.
Result
After that you could drag your (for example) PointVew control and the properties in your view model will be updated because of two way binding.
Suppose I understand correctly what do you want.
Added answers to the questions
Silverlight 5 supports it. That's mean all the items will be placed on the Canvas control. Some article about ItemsControl.
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas></Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
PointView is the second user control.
Note: I've described a way to draw an array of points with MVVM. You can drag each point on the canvas and receiving the new coordinates in your view model. (Maybe my description was a bit confusing on this stage so I've deleted LineViews from it)
In order to make a Lines, you have to connect your points. It'll be more difficult so I suggest you to make a variant with points only.
When you will be familiar with it, you can move your ItemsControl into templated control. Make your own ItemSource collection and drawing the Path by this points when they will change the position.
You can also search some opensource graph controls and see how they drawing curving lines by dots. Actually they usually do doing it with the Path like I have described.
Sorry, but I wouldn't write more because it'll became an article but not an answer)
P.S: It is interesting question, so If I have some free time I may be write an article. About templated controls you can read here.
It's absolutely disgusting how much XAML this takes. I'll look for a way to clean it up using styles and templates. Also, I need to draw the line to the center of the point, that shouldn't be hard. For now, below is what worked. I ended up created a Collection<Pair<Point, Point>> ViewModel to bind the "Line" collection. Otherwise I'm looking at the line point-by-point and can't draw a line since I can't find X2/Y2.
Thanks for the inspiration Alexander.
Here is the XAML:
<ItemsControl ItemsSource="{Binding Lines}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="viewModels:LineViewModel">
<ItemsControl ItemsSource="{Binding LineSegments}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl ItemsSource="{Binding Lines}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Line X1="{Binding Item1.X}" X2="{Binding Item2.X}" Y1="{Binding Item1.Y}" Y2="{Binding Item2.Y}" Stroke="Black" StrokeThickness="2"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl ItemsSource="{Binding LineSegment}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Canvas.Left="{Binding X}" Canvas.Top="{Binding Y}" Width="10" Height="10" Fill="Black">
<Ellipse.RenderTransform>
<TranslateTransform X="{Binding X}" Y="{Binding Y}"/>
</Ellipse.RenderTransform>
</Ellipse>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here is the ViewModel:
namespace CanvasBindTest.ViewModels
{
/// <summary>
/// Sample view model showing design-time resolution of data
/// </summary>
[ExportAsViewModel(typeof (MainViewModel))]
public class MainViewModel : BaseViewModel
{
public MainViewModel()
{
var start = new PointViewModel {X = 0, Y = 0};
var middle = new PointViewModel {X = 30, Y = 10};
var end = new PointViewModel {X = 20, Y = 0};
var simpleLine = new LineSegmentsViewModel(new[] {start, middle, end});
Lines = new ObservableCollection<LineViewModel> {new LineViewModel(new[] {simpleLine})};
}
public ObservableCollection<LineViewModel> Lines { get; private set; }
}
public class LineViewModel : BaseViewModel
{
public LineViewModel(IEnumerable<LineSegmentsViewModel> lineSegments)
{
LineSegments = new ObservableCollection<LineSegmentsViewModel>(lineSegments);
}
public ObservableCollection<LineSegmentsViewModel> LineSegments { get; private set; }
}
public class LineSegmentsViewModel : BaseViewModel
{
public LineSegmentsViewModel(IEnumerable<PointViewModel> lineSegment)
{
LineSegment = new ObservableCollection<PointViewModel>(lineSegment);
Lines = new Collection<Tuple<PointViewModel, PointViewModel>>();
var tmp = lineSegment.ToArray();
for (var i = 0; i < tmp.Length - 1; i++)
{
Lines.Add(new Tuple<PointViewModel, PointViewModel>(tmp[i], tmp[i+1]));
}
}
public Collection<Tuple<PointViewModel, PointViewModel>> Lines { get; private set; }
public ObservableCollection<PointViewModel> LineSegment { get; private set; }
}
public class PointViewModel : BaseViewModel
{
private int x, y;
public int X
{
get { return x; }
set
{
x = value;
RaisePropertyChanged(() => X);
}
}
public int Y
{
get { return y; }
set
{
y = value;
RaisePropertyChanged(() => Y);
}
}
}
}
I am using Caliburn Micro in my Project and i have many UserControls and thier viewmodel inherited from PropertyChangedBase, i want this UserControl to be added to a Canvas in my ShellView. I dont want to use IWindowManager from showing Windows instead i want them to get added in a Canvas.
Please help. How can i do that.
If you use ContentControl within your ShellView you can hook into the View-ViewModel binding process of Caliburn.Micro.
I assume that in your ShellViewModel you have a bunch of properties exposed that are types of ViewModel. If you place a ContentControl in your ShellView (this could be on/as a child of Canvas if that is the container you wish to use to layout your Shell), and then name that control with the name of the property in your ShellViewModel you wish it to be bound to, then Caliburn's ViewModelBinder will do the rest for you.
As an example say you have a VM called FizzViewModel and a matching View called FizzView (which is just a UserControl) and you want FizzView to appear on your ShellView you could do something like the following...
A stripped back ShellViewModel
public class ShellViewModel : Screen, IShell
{
public ShellViewModel(FizzViewModel theFizz)
{
TheFizz = theFizz;
}
public FizzViewModel TheFizz { get; set; }
}
And its matching ShellView
<UserControl x:Class="ANamespace.ShellView">
<Canvas>
<ContentControl x:Name="TheFizz"></ContentControl>
</Canvas>
</UserControl>
Here because the ContentControl is named TheFizz, it will be bound by Caliburn to the property with that name on your VM (the one of type FizzViewModel)
Doing this means you don't have to laydown your UserControl's using their true types on your ShellView, you let Caliburn do the work for you via conventions (which all so means its easy to swap out the type TheFizz if you just add a little more interface indirection).
UPDATE
From the extra information you have provided in the comments, I can now see you are actually looking at a problem that requires an ItemsControl.
The default DataTemplate Caliburn uses looks like the following
<DataTemplate xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro">
<ContentControl cal:View.Model="{Binding}"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch" />
</DataTemplate>
You will notice that it uses a ContentControl, which has some advantages as I have discussed above. Basically what this will do is allow Caliburn to provide DataTemplateSelector like behaviour to the items in your ItemsControl. So you can add VMs of different types to the collection your ItemsControl is bound to and this default DataTemplate will resolve the type of View to use to display it. The following demos a very simple example of how you can achieve what you want.
First the ShellViewModel, take note of the BindableCollection named Items
[Export(typeof(IShell))]
public class ShellViewModel : IShell
{
public ShellViewModel()
{
Items = new BindableCollection<Screen>();
_rand = new Random();
}
public BindableCollection<Screen> Items { get; set; }
private Random _rand;
public void AddItem()
{
var next = _rand.Next(3);
var mPosition = System.Windows.Input.Mouse.GetPosition(App.Current.MainWindow);
switch (next)
{
case 0:
{
Items.Add(new BlueViewModel
{
X = mPosition.X,
Y = mPosition.Y,
});
break;
}
case 1:
{
Items.Add(new RedViewModel
{
X = mPosition.X,
Y = mPosition.Y,
});
break;
}
case 2:
{
Items.Add(new GreenViewModel
{
X = mPosition.X,
Y = mPosition.Y,
});
break;
}
default:
break;
}
}
}
And then a few dummy VM types that you want to display in your Shell. These could be/do anything you like:
public abstract class SquareViewModel : Screen
{
public double X { get; set; }
public double Y { get; set; }
}
public class BlueViewModel : SquareViewModel
{
}
public class RedViewModel : SquareViewModel
{
}
public class GreenViewModel : SquareViewModel
{
}
Now a ShellView, note the ItemsControl which binds to the Items property on your ShellViewModel
<Window x:Class="WpfApplication2.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro">
<Grid >
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<ItemsControl x:Name="Items"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas cal:Message.Attach="[Event MouseLeftButtonUp] = [Action AddItem()]"
Background="Transparent"></Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=X}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Grid>
</Window>
And an example of a UserControl that will be used to display the GreenViewModel, create 2 more of these, changing the names to RedView and BlueView and set the backgrounds appropriately to get the demo to work.
<UserControl x:Class="WpfApplication2.GreenView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="30"
Height="30">
<Grid Background="Green"></Grid>
</UserControl>
What this example does when put together is creates colored squares on the Canvas of your shell based on the location of the mouse click. I think you should be able to take this and extend it to your needs.
(How long have we been using OpenIDs? I don't seem to have one connected to any provider and no FAQ on the planet seems to tell you what to do when you lose your OpenID. I've had to create a new account. Argh!)
I'm trying to fill a (carousel) listbox from a database, but I can't get the images to display. Every single example on the net assumes you have two or three jpegs in a folder instead of in memory :(
My item class is:
public class PicCollection : ObservableCollection<CarouselItem>
{
public PicCollection()
{
}
}
public class CarouselItem
{
public int ItemID { get; set; }
public string ItemName { get; set; }
public string ItemDesc { get; set; }
public Image ItemImage { get; set; }
public CarouselItem(int NewItemID, string NewItemName, string NewItemDesc, Image newItemImage)
{
this.ItemID = NewItemID;
this.ItemName = NewItemName;
this.ItemDesc = NewItemDesc;
this.ItemImage = newItemImage;
}
}
I fill a PicCollection successfully from the db(using a byte array for the image), and try to display the name and image with
<DataTemplate x:Key="TestDataTemplate2" >
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="CarName"
Text="{Binding ItemName}"
Padding="15,15"
Foreground="Yellow" />
<Image Source="{Binding Source=ItemImage}"
Stretch="Fill" />
</StackPanel>
</DataTemplate>
Listbox is simply:
<ListBox Name="lstTest" ItemsSource="{StaticResource TestDataSource}"
ItemTemplate="{StaticResource TestDataTemplate2}"></ListBox>
ItemName displays ok, ItemImage does not. I've tried {Binding ItemImage} as well. Most examples use {Binding Path=ItemImage} but that's where the ItemImage would be a string containing the path, not the actual image itself.
I'm pulling my hair out. Nothing I do makes the images appear. If I create an image control and manually assign the contents of an ItemImage, however, it displays fine. What am I doing wrong?
Your ItemImage property is an Image (control). Make it an ImageSource, such as a System.Windows.Media.Imaging.BitmapImage. Then you can bind <Image Source="{Binding ItemImage}" />.
Since your "ItemImage" property is already an image, you might be able to get away with just using a ContentControl to display the image directly:
<ContentControl Content="{Binding Image}" />