Simple databinding between slider and object to update a drawing in WPF - wpf

I have a specific need to use System.Drawing.Graphics to use as as ImageSource for an Image control. That graphics is to be updated by a slider, whose Value is to be data-bound to an object that acts as a model to build the drawing.
I have set up a minimal working version of what I want to accomplish, and have created as much "Binding Infrastructure" as I could, but have stopped where things started to get confusing for me. My code has just the MainWindow (XAML and code behind) and a Radius class:
MainWindow:
<Window x:Class="MinimalUpdateableDrawing.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
WindowState="Maximized">
<DockPanel>
<Slider x:Name="SizeSlider" DockPanel.Dock="Bottom" />
<Image x:Name="figure" Width="800" Height="600" />
</DockPanel>
</Window>
MainWindow codebehind:
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows;
using System.Windows.Media.Imaging;
namespace MinimalUpdateableDrawing {
public partial class MainWindow : Window {
Radius radius_instance;
public MainWindow() {
InitializeComponent();
radius_instance = new Radius();
this.Loaded +=new RoutedEventHandler(DrawCircle);
}
void DrawCircle(object sender, RoutedEventArgs e) {
int radius = radius_instance.Value;
using (var bmp = new Bitmap((int)figure.Width, (int)figure.Height)) {
using (var g = Graphics.FromImage(bmp)) {
g.FillEllipse(System.Drawing.Brushes.Blue,
(int)figure.Width/2-radius,
(int)figure.Height/2-radius,
radius*2, radius*2);
}
using(MemoryStream ms = new MemoryStream()) {
bmp.Save(ms, ImageFormat.Bmp);
ms.Position = 0;
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = ms;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
figure.Source = bitmapImage;
}
}
}
}
}
Radius class:
using System;
using System.ComponentModel;
namespace MinimalUpdateableDrawing
{
class Radius : INotifyPropertyChanged {
int _value = 100;
public int Value {
get { return _value; }
set {
_value = value;
OnPropertyChanged("Value");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string p) {
throw new NotImplementedException();
}
}
}
If anyone could suggest what I should do to implement the binding between SizeSlider and radius_instance.Value, so that when I move the slider the image updates, that would get me going!

If you were simply drawing an ellipse, I'd recommend binding both the slider's value and an Ellipse object to the object that stores the value in question. You can see more about drawing with Wpf at http://msdn.microsoft.com/en-us/library/ms747393.aspx.
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Ellipse Height="{Binding Radius}" Width="{Binding Radius}" HorizontalAlignment="Center" VerticalAlignment="Center" Fill="Black" />
<Slider Value="{Binding Radius}" Grid.Row="1" Minimum="0" Maximum="200" />
</Grid>
With that said, if the drawing was more complex, you may want to consider using an IValueConverter to adapt the value into a drawing you could use...for example:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:EllipseConverter x:Key="EllipseConverter" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Source="{Binding Radius, Converter={StaticResource EllipseConverter}}" HorizontalAlignment="Center" VerticalAlignment="Center" Stretch="None" />
<Slider Value="{Binding Radius}" Grid.Row="1" Minimum="0" Maximum="200" />
</Grid>
</Window>
IValueConverter:
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Data;
using System.Windows.Media.Imaging;
namespace WpfApplication1
{
class EllipseConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null) return null;
int radius = (int)value;
int diameter = radius * 2;
using (var bmp = new System.Drawing.Bitmap(diameter, diameter))
{
using (var g = Graphics.FromImage(bmp))
{
g.FillEllipse(System.Drawing.Brushes.Blue,
0,
0,
diameter,
diameter);
}
using (MemoryStream ms = new MemoryStream())
{
bmp.Save(ms, ImageFormat.Bmp);
ms.Position = 0;
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = ms;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
return bitmapImage;
}
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

Related

WPF and MVVM: Use RotateTransform and DoubleAnimation to move an object along circular path

I'm relatively new to WPF animations. I would like to have an object (a simple circle for example) move around a circular path in 10 degree increments. The following example is tantalizingly close: WPF circular moving object See the answer by Clemens who uses a RotateTransform in XAML and a DoubleAnimation in code-behind. I am however, using MVVM and it's not clear to me how to accomplish this. I believe I would need to access the RotateTransform which is in the View from my ViewModel, so I can call BeginAnimation, but how?
Any examples or ideas? I have searched without luck.
Thanks
UPDATE: I can now be more specific in my question by showing what I've already tried. Based on the above mentioned reference AND Twoway-bind view's DependencyProperty to viewmodel's property? (answer by #Michael Schnerring), I have the following simple code (below). My ellipse is not rotating. No binding errors, or any other errors, just no rotation. And my methods are hit (I debugged into it) I'm guessing my PerformAnimation function is incorrect, specifically the SetTargetProperty part. I did try to play with it by adding two animations (one for Rotation, one for Transform) but without luck.
Can someone give me an idea what I'm doing wrong?
XAML:
<Canvas Grid.Row="0" Grid.Column="0">
<Ellipse Height="100" Width="100" Fill="Aqua" Name="MyEllipse"
Canvas.Left="200" Canvas.Top="200"
RenderTransformOrigin="0.5,0.5">
<Ellipse.RenderTransform>
<TransformGroup>
<TranslateTransform Y="-100"/>
<RotateTransform />
</TransformGroup>
</Ellipse.RenderTransform>
</Ellipse>
</Canvas>
<Button Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" Command="{Binding Path=RotateCommand}">Press Me!</Button>
Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var nameOfPropertyInVm = "AngleProperty";
var binding = new Binding(nameOfPropertyInVm) { Mode = BindingMode.TwoWay };
this.SetBinding(AngleProperty, binding);
}
public double Angle
{
get { return (double)GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
}
// Using a DependencyProperty as the backing store for Angle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AngleProperty = DependencyProperty.Register("Angle", typeof(double), typeof(MainWindow), new UIPropertyMetadata(0.0, new PropertyChangedCallback(AngleChanged)));
private static void AngleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
MainWindow control = (MainWindow)sender;
control.PerformAnimation((double)e.OldValue, (double)e.NewValue);
}
private void PerformAnimation(double oldValue, double newValue)
{
Storyboard s = new Storyboard();
DoubleAnimation animation = new DoubleAnimation();
animation.From = oldValue;
animation.To = newValue;
animation.Duration = new Duration(TimeSpan.FromSeconds(1));
s.Children.Add(animation);
Storyboard.SetTarget(animation, MyEllipse);
Storyboard.SetTargetProperty(animation, new PropertyPath("(Ellipse.RenderTransform).(RotateTransform.Angle)"));
s.Begin();
}
And ViewModel
public class MyViewModel : ViewModelBase
{
private ICommand _rotateCommand = null;
private double _angleProperty = 10;
public MyViewModel()
{
}
public double AngleProperty
{
get
{
return _angleProperty;
}
set
{
_angleProperty = value;
OnPropertyChanged("AngleProperty");
}
}
public ICommand RotateCommand
{
get
{
if (_rotateCommand == null)
{
_rotateCommand = new RelayCommand(param => RotateCommandImplementation());
}
return _rotateCommand;
}
}
private void RotateCommandImplementation()
{
AngleProperty = AngleProperty + 10;
}
}
Here's my solution, based on a lot of help from #Clemens (see comments and WPF circular moving object)
VIEW
<Window x:Class="AnimationBindingPlay.MainWindow"
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:local="clr-namespace:AnimationBindingPlay"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Canvas Grid.Row="0" Grid.Column="0">
<Ellipse Height="100" Width="100" Fill="Aqua" Name="MyEllipse"
Canvas.Left="200" Canvas.Top="200"
RenderTransformOrigin="0.5,0.5">
<Ellipse.RenderTransform>
<TransformGroup>
<TranslateTransform Y="-100"/>
<RotateTransform x:Name="rotateTransform"/>
</TransformGroup>
</Ellipse.RenderTransform>
</Ellipse>
</Canvas>
<Button Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" Command="{Binding Path=RotateCommand}">Press Me!</Button>
</Grid>
Code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var nameOfPropertyInVm = "AngleProperty";
var binding = new Binding(nameOfPropertyInVm) { Mode = BindingMode.TwoWay };
this.SetBinding(AngleProperty, binding);
}
public double Angle
{
get { return (double)GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
}
// Using a DependencyProperty as the backing store for Angle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AngleProperty = DependencyProperty.Register("Angle", typeof(double), typeof(MainWindow), new UIPropertyMetadata(0.0, new PropertyChangedCallback(AngleChanged)));
private static void AngleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
MainWindow control = (MainWindow)sender;
control.PerformAnimation((double)e.OldValue, (double)e.NewValue);
}
private void PerformAnimation(double oldValue, double newValue)
{
var rotationAnimation = new DoubleAnimation(oldValue, newValue, TimeSpan.FromSeconds(1));
rotateTransform.BeginAnimation(RotateTransform.AngleProperty, rotationAnimation);
}
}
ViewModel
Same as in question!

How can I make the combobox add items faster?

I used a ComboBox to load all the font on the computer and preview it.
Here is the XAML:
<Window x:Class="Sample.MainWindow"
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:local="clr-namespace:Sample"
mc:Ignorable="d"
Title="Demo" Height="450" Width="800" WindowStartupLocation="CenterScreen" WindowStyle="None" Loaded="Window_Loaded" Background="White">
<Window.Resources>
<local:FontFamilyConverter x:Key="FontFamilyConverter"></local:FontFamilyConverter>
</Window.Resources>
<Grid>
<ComboBox Margin="10,10,0,10" HorizontalContentAlignment="Stretch" Name="FontFaimlyCB" Height="50" Width="250" ItemsSource="{Binding Source={x:Static Fonts.SystemFontFamilies}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" FontFamily="{Binding Converter={StaticResource FontFamilyConverter}}" FontSize="20"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Window>
And here is code-behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Threading;
using System.Globalization;
namespace Sample
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
}
}
public class FontFamilyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new FontFamily(value.ToString());
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
When the first time I click the ComboBox, it always takes a long time to add the items(There are almost one thousand fonts on my computer and it will takes 3-4 seconds to finish it). But any other software such as word/photoshop and so on won't like this.
How can I make it add faster? Please help me, thank you.
You could try to use a VirtualizingStackPanel as the ItemsPanel and set the MaxDropDownHeight to a fairly small value in order not to render all containers immediately. And you shouldn't have to use a converter:
<ComboBox Margin="10,10,0,10" HorizontalContentAlignment="Stretch" Name="FontFaimlyCB" Height="50" Width="250"
ItemsSource="{Binding Source={x:Static Fonts.SystemFontFamilies}}"
MaxDropDownHeight="50">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" FontFamily="{Binding}" FontSize="20" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Create your own observable collection that allows you to suspend notifications when adding items. E.g.:
static void Main(string[] args)
{
var fonts = new ObservableCollectionEx<string>();
using (fonts.DeferCollectionChanged())
{
for (int i = 0; i < 100000; i++)
{
fonts.Add(Guid.NewGuid().ToString());
}
}
}
public class ObservableCollectionEx<T> : ObservableCollection<T>
{
private int deferLevel;
private bool collectionUpdateNeeded;
public ObservableCollectionEx()
{
}
public ObservableCollectionEx(IEnumerable<T> collection)
: base(collection)
{
}
public ObservableCollectionEx(List<T> collection)
: base(collection)
{
}
public override event NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (deferLevel == 0)
{
CollectionChanged?.Invoke(this, e);
collectionUpdateNeeded = false;
}
else
{
collectionUpdateNeeded = true;
}
}
public IDisposable DeferCollectionChanged()
{
++deferLevel;
return new DeferHelper(this);
}
private void EndDefer()
{
--deferLevel;
if (deferLevel == 0 && collectionUpdateNeeded)
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
private class DeferHelper : IDisposable
{
private ObservableCollectionEx<T> collection;
public DeferHelper(ObservableCollectionEx<T> collection)
{
this.collection = collection;
}
public void Dispose()
{
if (collection != null)
{
collection.EndDefer();
collection = null;
}
}
}
}

Variable Image Source inside a Button Template

How can I transfer the Source attribute from a button template to an Image inside that template ?
This is how my XAML looks like:
<Page x:Class="TallShip.MainGallery"
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:local="clr-namespace:TallShip"
mc:Ignorable="d"
d:DesignHeight="1080" d:DesignWidth="1920"
Title="MainGallery">
<Grid>
<ScrollViewer Height="958" HorizontalAlignment="Left" Margin="685,87,0,0" Name="GalleryRightScroller" VerticalAlignment="Top" Width="1159" PanningMode="VerticalOnly" VerticalScrollBarVisibility="Auto">
<Grid>
<Grid.Resources>
<ControlTemplate x:Key="GalleryObject">
<StackPanel Height="Auto" HorizontalAlignment="Left" Margin="{TemplateBinding Margin}" VerticalAlignment="Top" Width="500">
<Image Height="200" Stretch="None" Source="{TemplateBinding local:SourceHolder.Source}" Width="200" />
<Label Content="{TemplateBinding local:SourceHolder.Source}" Height="28" Name="label1" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" />
</StackPanel>
</ControlTemplate>
</Grid.Resources>
<Button Template="{StaticResource GalleryObject}" local:SourceHolder.Source="/TallShip;component/media/91gx2EQ.jpg" Name="object1" Margin="43,21,0,0"/>
<Button Template="{StaticResource GalleryObject}" local:SourceHolder.Source="/TallShip;component/media/91gx2EQ.jpg" Name="object2" Margin="43,280,0,0"/>
</Grid>
</ScrollViewer>
</Grid>
And this is the custom control class I've created to hold the Source property:
namespace TallShip {
public static class SourceHolder
{
public static readonly DependencyProperty SourceProperty = DependencyProperty.RegisterAttached("Source",
typeof(string), typeof(SourceHolder), new FrameworkPropertyMetadata(null));
public static string GetSource(UIElement element)
{
if (element == null)
throw new ArgumentNullException("element");
return (string)element.GetValue(SourceProperty);
}
public static void SetSource(UIElement element, string value)
{
if (element == null)
throw new ArgumentNullException("element");
element.SetValue(SourceProperty, value);
}
}
}
Currently, the label correctly displays the passed source, but the image does not work...
If I use the same source on an image outside of the button template, the image works just fine.
Option 1:
<Image Height="200" Stretch="None" DataContext="{TemplateBinding local:SourceHolder.Source}" Width="200" Source="{Binding}"></Image>
Option 2:
If that does not work, Use other alternative of using ValueConverter in Image binding that converts uri to Image
public class UriToImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return null;
if (value is string)
value = new Uri((string)value, UriKind.RelativeOrAbsolute);
if (value is Uri)
{
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.UriSource = (Uri)value;
bi.EndInit();
return bi;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
<Window.Resources>
<local:UriToImageConverter x:Key="uriToImageConverter"></local:UriToImageConverter>
</Window.Resources>
<Image Height="200" Stretch="None" DataContext="{TemplateBinding local:SourceHolder.Source}" Width="200" Source="{Binding Converter={StaticResource ResourceKey=uriToImageConverter}}"></Image>

Wpf ItemsControl.ItemsPanelTemplate as a Grid and add new items in different columns

I'm working on a dynamicaly created grid containing in each new column a new item added to an ItemsControl.
I'm using Rachel Lim's GridHelper
For now, i have a main window like following,
Xaml code:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:StarsConverter x:Key="conv"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="9*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Command="{Binding Add}"/>
<ItemsControl x:Name="list" Grid.Column="1" ItemsSource="{Binding Oc}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid x:Name="grid" Background="Black"
local:GridHelpers.StarColumns="{Binding ColumnCount, Converter={StaticResource conv}, Mode=OneWay}" local:GridHelpers.ColumnCount="{Binding ColumnCount}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas Loaded="canvas_Loaded" x:Name="canvas" Background="White">
<Border Canvas.Left="25" Canvas.Top="25" Height="25" Width="50" Background="Red">
<TextBlock Text="{Binding}"/>
</Border>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
and Xaml.cs
public partial class MainWindow : Window
{
private Canvas canvas;
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
private void canvas_Loaded(object sender, RoutedEventArgs e)
{
ViewModel vm = this.DataContext as ViewModel;
canvas = sender as Canvas;
Binding b = new Binding();
b.Source = vm;
b.Path = new PropertyPath(ViewModel.ColumnCountProperty);
b.Mode = BindingMode.OneWay;
canvas.SetBinding(Grid.ColumnProperty, b);
}
}
This MainWindow has a ViewModel as DataContext:
public class ViewModel : DependencyObject
{
private RelayCommand add;
public ViewModel()
{
}
public ObservableCollection<String> Oc
{
get { return (ObservableCollection<String>)GetValue(OcProperty); }
set { SetValue(OcProperty, value); }
}
// Using a DependencyProperty as the backing store for Oc. This enables animation, styling, binding, etc...
public static readonly DependencyProperty OcProperty =
DependencyProperty.Register("Oc", typeof(ObservableCollection<String>), typeof(ViewModel), new UIPropertyMetadata(new ObservableCollection<string>()));
public int ColumnCount
{
get { return (int)GetValue(ColumnCountProperty); }
set { SetValue(ColumnCountProperty, value); }
}
// Using a DependencyProperty as the backing store for ColumnCount. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ColumnCountProperty =
DependencyProperty.Register("ColumnCount", typeof(int), typeof(ViewModel), new UIPropertyMetadata(0));
public RelayCommand Add
{
get
{
if (this.add == null)
this.add = new RelayCommand(param => this.AddString(), param => this.CanAddString());
return this.add;
}
}
private bool CanAddString()
{
return true;
}
private void AddString()
{
this.Oc.Add("test" + ColumnCount);
ColumnCount++;
}
}
When i click the button, the command is doing fine, so i have a new itemscontrol item and my grid as ItemsPanelTemplate is updating with new ColumnDefinition but the item is not in the right column.
I know its a late answer :)
A more elegant solution would be adding two Dependency Properties Row and Column to your Items ViewModel
then in your ItemsControl you define an ItemContainerStyle:
<ItemsControl>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Row" Value="{Binding Path=Row}" />
<Setter Property="Grid.Column" Value="{Binding Path=Column}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
when you populate your ItemsSource collection you have set The Row and Column properties:
for (uint i = 0; i < yourCollection.Count; ++i )
{
var item = yourCollection[i];
item.Row = (int)(i / ColumnCount);
item.Column = (int)(i % ColumnCount);
}
In fact the canvas_loaded thing is useless...
The problem was coming from the GridHelpers class in the ColumnCountChanged.
I added this at the end of the ColumnCountChanged event after SetStarColumns(grid) in order to add the items in the corresponding cells:
for (int i = 0; i < childCount; i++)
{
var child = grid.Children[i] as FrameworkElement;
Grid.SetColumn(child, i);
}
and it worked!
Edit: I also used a converter (called "conv" in the Xaml sample above) for the OneWay binding of the GridHelper.StarColumns Property on a ColumnCount ViewModel Property to have all the newly created columns to be equally sized:
public class StarsConverter : IValueConverter
{
public object Convert(object value, Type TargetType, object parameter, CultureInfo culture)
{
int i = (int)value;
string s = "0";
for (int j = 1; j < i; j++)
{
s = s + ',' + j;
}
return s;
}
public object ConvertBack(object value, Type TargetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

Display Images (Binding) in Silverlight

I need to solve following issue: In a Silverlight app, the user needs to be able to upload images to the client (not the server). The images should be displayed in a wrappanel.
I am a complete rookie to silverlight, so you might find several mistakes in the code. Below I posted the code, I hope it is sufficient to answer the question!
App.xaml:
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Dojo_3_wi10b012.ViewModel"
x:Class="Dojo_3_wi10b012.App"
>
<Application.Resources>
<local:ViewModel x:Key="viewModel"/>
</Application.Resources>
</Application>
MainPage.xaml:
<UserControl x:Class="Dojo_3_wi10b012.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:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:local="clr-namespace:Dojo_3_wi10b012.ViewModel"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White" DataContext="{StaticResource viewModel}">
<sdk:Frame Height="350" Width="450"
Name="frameContainer" Margin="0,0,0,0">
</sdk:Frame>
<Button Content="Add Image" Command="{Binding Path=AddImageCommand}" Height="23" HorizontalAlignment="Left" Margin="468,454,0,0" Name="buttonAddImage" VerticalAlignment="Top" Width="75" />
</Grid>
</UserControl>
Gallery.xaml:
<navigation:Page x:Class="Dojo_3_wi10b012.View.Gallery"
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:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:Dojo_3_wi10b012.ViewModel"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignWidth="640" d:DesignHeight="480"
Title="Gallery Page"
NavigationCacheMode="Required" xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit">
<!-- trying to implement Mr. Eckkrammer's hint (wiki)-->
<UserControl.Resources>
<DataTemplate x:Key="galleryTemplate">
<Image>
<Image.Source>
<BitmapImage UriSource="{Binding Image}"/>
</Image.Source>
</Image>
</DataTemplate>
</UserControl.Resources>
<!-- END OF trying to implement Mr. Eckkrammer's hint (wiki)-->
<Grid x:Name="LayoutRoot" Height="350" Width="450" Background="Beige" DataContext="{StaticResource viewModel}">
<!-- "First attempt
<ItemsControl ItemsSource="{Binding ElementName=ImageDescrList, Path=Image ,Mode=TwoWay}" >
<ScrollViewer Width="449" Height="349" Margin="1,1,0,0">
<toolkit:WrapPanel Orientation="Horizontal" ItemWidth="90" ItemHeight="90" Width="Auto" />
</ScrollViewer>
</ItemsControl>
-->
<!-- 2nd attempt (Mr. Eckkrammer's hint (wiki): -->
<ItemsControl ItemsSource="{Binding ElementName=ImageDescrList}" ItemTemplate="{StaticResource galleryTemplate}">
<ScrollViewer Width="449" Height="349" Margin="1,1,0,0">
<toolkit:WrapPanel Orientation="Horizontal" ItemWidth="90" ItemHeight="90" Width="Auto" />
</ScrollViewer>
</ItemsControl>
</Grid>
</navigation:Page>
ImageDescription.cs:
namespace Dojo_3_wi10b012.Model
{
public class ImageDescription : INotifyPropertyChanged
{
private string _description;
private BitmapImage _image;
public string Description
{
get { return _description; }
set
{
_description = value;
NotifyPropertyChanged("Description");
}
}
public BitmapImage Image
{
get { return _image; }
set
{
_image = value;
NotifyPropertyChanged("Image");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
ViewModel.cs:
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.IO;
using System.IO.IsolatedStorage;
using System.Windows.Media.Imaging;
using System.Collections;
using System.Collections.ObjectModel;
using Dojo_3_wi10b012.Model;
using System.ComponentModel;
namespace Dojo_3_wi10b012.ViewModel
{
public class ViewModel : INotifyPropertyChanged
{
public RelayCommand AddImageCommand { get; private set; }
private ObservableCollection<ImageDescription> _imageDescrList = new ObservableCollection<ImageDescription>();
#region properties
public ObservableCollection<ImageDescription> ImageDescrList
{
get { return _imageDescrList; }
set
{
_imageDescrList = value;
NotifyPropertyChanged("ImageDescrList");
}
}
#endregion
#region Constructor
public ViewModel()
{
AddImageCommand = new RelayCommand(AddImage);
AddImageCommand.IsEnabled = true;
}
#endregion
#region private methods
private void AddImage()
{
//initial ideas: (copy paste + modified from source: http://msdn.microsoft.com/en-us/library/cc221415(v=vs.95).aspx )
// Create an instance of the open file dialog box.
OpenFileDialog openFileDialog1 = new OpenFileDialog();
// Set filter options and filter index.
openFileDialog1.Filter = "JPEG Files (.jpg)|*.jpg|PNG Files (.png)|*.png|GIF Files (.gif)|*.gif|BMP Files (.bmp)|*.bmp|TIFF Files (.tiff)|*.tiff";
openFileDialog1.FilterIndex = 1;
openFileDialog1.Multiselect = false;
// Call the ShowDialog method to show the dialog box.
bool? userClickedOK = openFileDialog1.ShowDialog();
// Process input if the user clicked OK.
if (userClickedOK == true)
{
// Open the selected file to read.
using (var filestream = openFileDialog1.File.OpenRead())
{
var buffer = new byte[filestream.Length];
filestream.Read(buffer, 0, buffer.Length);
//add the image to our list of images
MemoryStream ms = new MemoryStream(buffer, 0, buffer.Length);
BitmapImage temp = new BitmapImage();
temp.SetSource(ms);
ImageDescrList.Add(
new ImageDescription()
{
// Description = filestream.Name,
Description="Default Description",
Image = temp
}
);
ms.Close();
filestream.Close();
}
}
}
#endregion
#region event Handler (property changed)
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
Try this in your galleryTemplate template to bind the Image Source to your bitmap:
<DataTemplate x:Key="galleryTemplate">
<Image Source="{Binding Image}"/>
</DataTemplate>
You appear to be trying to bind a bitmap UriSource to a Bitmap instead (that would expect a Uri):
<DataTemplate x:Key="galleryTemplate">
<Image>
<Image.Source>
<BitmapImage UriSource="{Binding Image}"/>
</Image.Source>
</Image>
</DataTemplate>

Resources