ListBox and Popup do not work together - wpf

I am using Caliburn.Micro for a WPF application.
One of the feature I am using wraps a view in the a popup control and let me choose the settings.
But I came to the conclusion that this is a behaviour I cannot solve.
In the code behind when I click the Button a popup appears and when I click elsewhere on the screen it goes away as expected.
When I click on an item in the list, the popup appears but does not disappear if I click elsewhere on the window. (it does though if i click somewhere else, like another application)
Basically I cannot create a PopUp with StaysOpen = false behaviour when handling a selected item event. What can i do to solve this?
<Window x:Class="WpfApplication1.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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox Name="ListBox" Width="250" HorizontalAlignment="Left" SelectionChanged="ListBox_SelectionChanged">
</ListBox>
<Button Name="Button" HorizontalAlignment="Right" Content="ShowPopUp" Click="Button_Click" />
</Grid>
</Window>
and the code behind
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.ListBox.ItemsSource = new System.Collections.ObjectModel.ObservableCollection<MainWindowItem>()
{
new MainWindowItem {Name = "Test 001" },
new MainWindowItem {Name = "Test 002" },
new MainWindowItem {Name = "Test 003" },
};
}
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selection = this.ListBox.SelectedItem as MainWindowItem;
if (selection == null)
{
return;
}
var popUp = new System.Windows.Controls.Primitives.Popup();
popUp.Child = new TextBox { Text = selection.Name , MinWidth = 200 , MinHeight = 32};
popUp.StaysOpen = false;
popUp.AllowsTransparency = true;
popUp.PlacementTarget = sender as FrameworkElement;
popUp.IsOpen = true;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var popUp = new System.Windows.Controls.Primitives.Popup();
popUp.Child = new TextBox { Text = "This is button Clicked" , MinWidth = 200, MinHeight = 32 };
popUp.StaysOpen = false;
popUp.AllowsTransparency = true;
popUp.PlacementTarget = sender as FrameworkElement;
popUp.IsOpen = true;
}
}
public class MainWindowItem
{
public string Name { get; set; }
public string Value { get; set; }
}
}

Hi please try the next solution. It based on the mouse down event hooking (from here), each time it happened will collapse the Popup(and reset list item selection).
Code behind
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow
{
public static readonly DependencyProperty SelectedProperty = DependencyProperty.Register(
"Selected", typeof (object), typeof (MainWindow), new PropertyMetadata(default(object)));
//property to monitor selection
public object Selected
{
get { return (object) GetValue(SelectedProperty); }
set { SetValue(SelectedProperty, value); }
}
private Popup _popUp;
public MainWindow()
{
InitializeComponent();
_popUp = new Popup {IsOpen = false};
ListBox.ItemsSource = new System.Collections.ObjectModel.ObservableCollection<MainWindowItem>()
{
new MainWindowItem {Name = "Test 001" },
new MainWindowItem {Name = "Test 002" },
new MainWindowItem {Name = "Test 003" },
};
Loaded += OnLoaded;
Unloaded += OnUnloaded;
}
//will stop hookig here
private void OnUnloaded(object sender, RoutedEventArgs routedEventArgs)
{
Unloaded -= OnUnloaded;
Loaded -= OnLoaded;
MouseHook.MouseAction -= MouseHookOnMouseAction;
MouseHook.Stop();
}
//will collapse the popup and reset selection
private void MouseHookOnMouseAction(object sender, EventArgs eventArgs)
{
Selected = null;
_popUp.IsOpen = false;
}
//will start hookig here
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
MouseHook.MouseAction += MouseHookOnMouseAction;
MouseHook.Start();
}
//here is your logic without any changes
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selection = ListBox.SelectedItem as MainWindowItem;
if (selection == null)
{
return;
}
_popUp.Child = new TextBox { Text = selection.Name, MinWidth = 200, MinHeight = 32 };
_popUp.StaysOpen = false;
_popUp.AllowsTransparency = true;
_popUp.PlacementTarget = sender as FrameworkElement;
_popUp.IsOpen = true;
}
//here is your logic without any changes
private void Button_Click(object sender, RoutedEventArgs e)
{
_popUp.IsOpen = false;
_popUp.Child = new TextBox { Text = "This is button Clicked", MinWidth = 200, MinHeight = 32 };
_popUp.StaysOpen = false;
_popUp.AllowsTransparency = true;
_popUp.PlacementTarget = sender as FrameworkElement;
_popUp.IsOpen = true;
}
}
public class MainWindowItem
{
public string Name { get; set; }
public string Value { get; set; }
}
/// <summary>
/// Mouse hooking helper
/// </summary>
public static class MouseHook
{
public static event EventHandler MouseAction = delegate { };
/// <summary>
/// Starts hooking
/// </summary>
public static void Start()
{
_hookID = SetHook(_proc);
}
/// <summary>
/// Stops hooking
/// </summary>
public static void Stop()
{
UnhookWindowsHookEx(_hookID);
}
private static LowLevelMouseProc _proc = HookCallback;
private static IntPtr _hookID = IntPtr.Zero;
//actually set a hook
private static IntPtr SetHook(LowLevelMouseProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
IntPtr hook = SetWindowsHookEx(WH_MOUSE_LL, proc, GetModuleHandle("user32"), 0);
if (hook == IntPtr.Zero) throw new System.ComponentModel.Win32Exception();
return hook;
}
}
private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);
//is the mouse hook callback
private static IntPtr HookCallback(
int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && MouseMessages.WM_LBUTTONDOWN == (MouseMessages)wParam)
{
MSLLHOOKSTRUCT hookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
MouseAction(null, new EventArgs());
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
private const int WH_MOUSE_LL = 14;
private enum MouseMessages
{
WM_LBUTTONDOWN = 0x0201,
WM_LBUTTONUP = 0x0202,
WM_MOUSEMOVE = 0x0200,
WM_MOUSEWHEEL = 0x020A,
WM_RBUTTONDOWN = 0x0204,
WM_RBUTTONUP = 0x0205
}
[StructLayout(LayoutKind.Sequential)]
private struct POINT
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
private struct MSLLHOOKSTRUCT
{
public POINT pt;
public uint mouseData;
public uint flags;
public uint time;
public IntPtr dwExtraInfo;
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook,
LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
}
XAML code
<Window x:Class="WPFPopupStrangeIssueSOHelpAttempt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" x:Name="This">
<Grid>
<ListBox Name="ListBox" Width="250" HorizontalAlignment="Left"
SelectedItem="{Binding ElementName=This, Path=Selected, UpdateSourceTrigger=PropertyChanged}"
SelectionChanged="ListBox_SelectionChanged">
</ListBox>
<Button Name="Button" HorizontalAlignment="Right" Content="ShowPopUp" Click="Button_Click" />
</Grid></Window>
Let me know if you need any explanations.
Regards.

Related

WPF insert non english text into listview

SO i have simple ListView with binding:
View model:
ObservableCollection<ClipboardItem> Clipboards;
ListView:
<ListView Name="ListViewUsers"
ItemsSource="{Binding Clipboards}"/>
<ListView.View>
<GridView >
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<Grid>
<TextBlock Text="{Binding Path=Text}"
Foreground="{Binding Path=Foreground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListViewItem}}}"
Margin="0,0,0,0"/>
</Grid>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
And all my non english text (Hebrew) is display as ???
Any suggestions how to fix it ?
Edit
I added my ClipboardItem class:
public class ClipboardItem
{
public string Text { get; set; }
}
I also add my Clipboard event:
private void ClipboardMonitor_OnClipboardContentChanged(object sender, EventArgs e)
{
string clipboardText = Clipboard.GetText(TextDataFormat.Text);
viewModel.Clipboards.Add(new ClipboardItem { Text = clipboardText });
}
ClipboardMonitor:
public sealed class ClipboardMonitor : IDisposable
{
private static class NativeMethods
{
/// <summary>
/// Places the given window in the system-maintained clipboard format listener list.
/// </summary>
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool AddClipboardFormatListener(IntPtr hwnd);
/// <summary>
/// Removes the given window from the system-maintained clipboard format listener list.
/// </summary>
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool RemoveClipboardFormatListener(IntPtr hwnd);
/// <summary>
/// Sent when the contents of the clipboard have changed.
/// </summary>
public const int WM_CLIPBOARDUPDATE = 0x031D;
/// <summary>
/// To find message-only windows, specify HWND_MESSAGE in the hwndParent parameter of the FindWindowEx function.
/// </summary>
public static IntPtr HWND_MESSAGE = new IntPtr(-3);
}
private HwndSource hwndSource = new HwndSource(0, 0, 0, 0, 0, 0, 0, null, NativeMethods.HWND_MESSAGE);
public ClipboardMonitor()
{
hwndSource.AddHook(WndProc);
NativeMethods.AddClipboardFormatListener(hwndSource.Handle);
}
public void Dispose()
{
NativeMethods.RemoveClipboardFormatListener(hwndSource.Handle);
hwndSource.RemoveHook(WndProc);
hwndSource.Dispose();
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == NativeMethods.WM_CLIPBOARDUPDATE)
{
OnClipboardContentChanged?.Invoke(this, EventArgs.Empty);
}
return IntPtr.Zero;
}
/// <summary>
/// Occurs when the clipboard content changes.
/// </summary>
public event EventHandler OnClipboardContentChanged;
}
edit
private ClipboardMonitor clipboardMonitor;
public MainWindow()
{
InitializeComponent();
InitiateClipboardMonitor();
}
private void InitiateClipboardMonitor()
{
clipboardMonitor = new ClipboardMonitor();
clipboardMonitor.OnClipboardContentChanged += ClipboardMonitor_OnClipboardContentChanged;
}
Changed the TextDataFormat.Text to TextDataFormat.UnicodeText
private void ClipboardMonitor_OnClipboardContentChanged(object sender, EventArgs e)
{
string clipboardText = Clipboard.GetText(TextDataFormat.UnicodeText);
Clipboards.Add(new ClipboardItem { Text = clipboardText });
}

WPF issue with a resizable / collapsible usercontrol

I'm trying to create a usercontrol that can resize and collapse/expand (with an animation). The resizing stop working when I play the collapse/expand animation.
Complete test application can be found here: App
EDIT: here's the relevant code as requested
MyControl.xaml:
<UserControl x:Class="WpfApp1.MyControl"
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:WpfApp1"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300">
<Grid Background="#FF935E5E">
<Thumb Width="8"
HorizontalAlignment="Right"
Margin="0,0,-4,0"
DragDelta="Thumb_DragDelta"
Cursor="SizeWE"/>
</Grid>
MyControl.xaml.cs:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MyControl.xaml
/// </summary>
public partial class MyControl : UserControl
{
public bool IsOpen
{
get { return (bool)GetValue(IsOpenProperty); }
set { SetValue(IsOpenProperty, value); }
}
public static readonly DependencyProperty IsOpenProperty =
DependencyProperty.Register("IsOpen", typeof(bool), typeof(MyControl), new PropertyMetadata(true, OnIsOpenChanged));
private static void OnIsOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MyControl control = d as MyControl;
control.PlayAnimation();
}
public double OpenWidth
{
get { return (double)GetValue(OpenWidthProperty); }
set { SetValue(OpenWidthProperty, value); }
}
public static readonly DependencyProperty OpenWidthProperty =
DependencyProperty.Register("OpenWidth", typeof(double), typeof(MyControl), new PropertyMetadata(300d, OnOpenWidthChanged));
private static void OnOpenWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MyControl control = d as MyControl;
if (control.IsOpen)
control.Width = control.OpenWidth;
}
public MyControl()
{
InitializeComponent();
if (IsOpen)
Width = OpenWidth;
}
private void Thumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
OpenWidth += e.HorizontalChange;
}
private void PlayAnimation()
{
DoubleAnimation sizeAnimation = new DoubleAnimation(IsOpen ? OpenWidth : 0, TimeSpan.FromMilliseconds(250));
sizeAnimation.EasingFunction = new CircleEase() { EasingMode = EasingMode.EaseInOut };
BeginAnimation(WidthProperty, sizeAnimation);
}
}
}
MainWindow.xaml:
<Window x:Class="WpfApp1.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:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="700">
<DockPanel>
<local:MyControl IsOpen="{Binding ControlIsOpen}"
OpenWidth="{Binding ControlOpenWidth}"/>
<Grid Background="Green">
<Button Width="100"
Height="20"
Content="Test Animation"
Click="Button_Click"/>
</Grid>
</DockPanel>
MainWindow.xaml.cs:
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
#endregion INotifyPropertyChanged
private bool _ControlIsOpen = true;
public bool ControlIsOpen
{
get => _ControlIsOpen;
set
{
_ControlIsOpen = value;
OnPropertyChanged();
}
}
private double _ControlOpenWidth = 300d;
public double ControlOpenWidth
{
get => _ControlOpenWidth;
set
{
_ControlOpenWidth = value;
OnPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
ControlIsOpen = !ControlIsOpen;
}
}
}
Thanks for the help :)
The animation actually never stopsYou should specifiy FillBehavior to Stop. In this case the annimation will stop updating the property after the final value is reached.
private void PlayAnimation()
{
DoubleAnimation sizeAnimation = new DoubleAnimation(IsOpen ? OpenWidth : 0, TimeSpan.FromMilliseconds(250));
sizeAnimation.FillBehavior = FillBehavior.Stop;
sizeAnimation.EasingFunction = new CircleEase() { EasingMode = EasingMode.EaseInOut };
sizeAnimation.Completed += OnAnimationCompleted;
BeginAnimation(WidthProperty, sizeAnimation);
}
private void OnAnimationCompleted(object sender, EventArgs e)
{
Width = IsOpen ? OpenWidth : 0;
}
The default value is HoldEnd. And the storyboard will modify the Width untill it is not explicitly stopped.
https://msdn.microsoft.com/en-us/library/system.windows.media.animation.timeline.fillbehavior(v=vs.110).aspx
Some more info https://learn.microsoft.com/en-us/dotnet/framework/wpf/graphics-multimedia/how-to-set-a-property-after-animating-it-with-a-storyboard
Well thanks to Dmitry idea, i've been able to solve it, by setting the fill behavior to stop and forcing to width to be either 0 or the open width:
private void PlayAnimation()
{
DoubleAnimation sizeAnimation = new DoubleAnimation(IsOpen ? OpenWidth : 0, TimeSpan.FromMilliseconds(250));
sizeAnimation.EasingFunction = new CircleEase() { EasingMode = EasingMode.EaseInOut };
sizeAnimation.FillBehavior = FillBehavior.Stop;
sizeAnimation.Completed += (s, e) => Width = (IsOpen ? OpenWidth : 0);
BeginAnimation(WidthProperty, sizeAnimation);
}
Thanks all :)

Disable Context Menu with WindowChrome.WindowChrome

I am unable to disable ContextMenu, if I use WindowChrome.WindowChrome style.
Please let me know your input to resolve this problem.
Without WindowChrome i am able to disable context menu with same piece of code.
<Window x:Class="ConextMenu_Sample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="35"
CornerRadius="0"
ResizeBorderThickness="5"
UseAeroCaptionButtons="False" />
</WindowChrome.WindowChrome>
<Grid>
</Grid>
</Window>
Code behind
public partial class MainWindow : Window
{
private const uint WP_SYSTEMMENU = 0x02;
private const uint WM_SYSTEMMENU = 0xa4;
public MainWindow()
{
InitializeComponent();
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
IntPtr windIntPtr = new WindowInteropHelper(this).Handle;
HwndSource hwndSource = HwndSource.FromHwnd(windIntPtr);
hwndSource.AddHook(new HwndSourceHook(WndProc));
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled)
{
if ((msg == WM_SYSTEMMENU) && (wparam.ToInt32() == WP_SYSTEMMENU))
{
handled = true;
}
return IntPtr.Zero;
}
}
Perhaps you didn't need this,but I solved it and want to share it to someone need it.
if (((msg == WM_SYSTEMMENU) && (wParam.ToInt32() == WP_SYSTEMMENU)) || msg == 165){
//ShowContextMenu();
handled = true;
}
Use this Code:
U need to set the windowstyle property None.
Property:- WindowStyle = "None"
<Window x:Class="ConextMenu_Sample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" WindowStyle="None">
[OR]
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled)
{
if ((msg == WM_SYSTEMMENU) && (wparam.ToInt32() == WP_SYSTEMMENU))
{
handled = false; // Change the Boolean Value to false //
}
return IntPtr.Zero;
}

How to load different images on different events of a button control using MVVM?

Using MVVM architecture, I want to load an image on a button.
As I have similar button in many places I want to make this as common.
1) Initially when the form is launched an image should be loaded.
2) When "MouseEnter" event then cursor image should change to hand.
3) When "MouseLeave" event then cursor image should change to Arrow.
4) When "MouseLeftButtonDown" then an image should be loaded.
5) When "MouseLeftButtonUp" then an image should be loaded.
Like this, I want to load different images on different events and this button needs to be used in
many other dialogs. How can design it such that it can be as a common resource for all.
Could any one please help me to solve this issue.
I am very new to WPF. Please be kind with your answers.
You could use attached properties for this. If you need different events, then you will have to add more attached properties.
Note that #2 and #3 could simply be Cursor="Hand" on the button. There is nothing to implement for #3 - the cursor will change on it's own when you leave the button. I added it as an attached property just for fun. It is completely useless and really just duplicates Cursor="Hand".
disk_blue.png, disk_green.png and disk_yellow.png were added to the root of the application by just dragging and dropping them onto the project in Visual Studio. The build action is "Resource".
Here is a complete demo app:
XAML:
<Window x:Class="WpfApplication56.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:WpfApplication56"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:VM InitialImage="disk_blue.png" MouseLeftButtonDownImage="disk_green.png" MouseLeftButtonUpImage="disk_yellow.png" />
</Window.DataContext>
<Grid>
<Button VerticalAlignment="Center" HorizontalAlignment="Center" Padding="20"
local:MyButton.MouseEnterCursor="Hand"
local:MyButton.InitialImage="{Binding InitialImage}"
local:MyButton.MouseLeftButtonDown="{Binding MouseLeftButtonDownImage}"
local:MyButton.MouseLeftButtonUp="{Binding MouseLeftButtonUpImage}"
/>
</Grid>
</Window>
CS:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Imaging;
namespace WpfApplication56
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class VM // I didn't implement INotifyPropertyChanged because it doesn't matter for this question
{
public Uri InitialImage { get; set; }
public Uri MouseLeftButtonDownImage { get; set; }
public Uri MouseLeftButtonUpImage { get; set; }
}
public class MyButton
{
// Attached Properties
#region MouseEnterCursorProperty
public static Cursor GetMouseEnterCursor(DependencyObject obj)
{
return (Cursor)obj.GetValue(MouseEnterCursorProperty);
}
public static void SetMouseEnterCursor(DependencyObject obj, Cursor value)
{
obj.SetValue(MouseEnterCursorProperty, value);
}
public static readonly DependencyProperty MouseEnterCursorProperty = DependencyProperty.RegisterAttached("MouseEnterCursor", typeof(Cursor), typeof(MyButton), new PropertyMetadata(null, OnPropertyChanged));
#endregion
#region InitialImageProperty
public static Uri GetInitialImage(DependencyObject obj)
{
return (Uri)obj.GetValue(InitialImageProperty);
}
public static void SetInitialImage(DependencyObject obj, Uri value)
{
obj.SetValue(InitialImageProperty, value);
}
public static readonly DependencyProperty InitialImageProperty = DependencyProperty.RegisterAttached("InitialImage", typeof(Uri), typeof(MyButton), new PropertyMetadata(null, OnPropertyChanged));
#endregion
#region MouseLeftButtonDownProperty
public static Uri GetMouseLeftButtonDown(DependencyObject obj)
{
return (Uri)obj.GetValue(MouseLeftButtonDownProperty);
}
public static void SetMouseLeftButtonDown(DependencyObject obj, Uri value)
{
obj.SetValue(MouseLeftButtonDownProperty, value);
}
public static readonly DependencyProperty MouseLeftButtonDownProperty = DependencyProperty.RegisterAttached("MouseLeftButtonDown", typeof(Uri), typeof(MyButton), new PropertyMetadata(null, OnPropertyChanged));
#endregion
#region MouseLeftButtonUpProperty
public static Uri GetMouseLeftButtonUp(DependencyObject obj)
{
return (Uri)obj.GetValue(MouseLeftButtonUpProperty);
}
public static void SetMouseLeftButtonUp(DependencyObject obj, Uri value)
{
obj.SetValue(MouseLeftButtonUpProperty, value);
}
public static readonly DependencyProperty MouseLeftButtonUpProperty = DependencyProperty.RegisterAttached("MouseLeftButtonUp", typeof(Uri), typeof(MyButton), new PropertyMetadata(null, OnPropertyChanged));
#endregion
// Called when the value for an attached property has changed
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Button b = d as Button;
if (b == null)
return;
if (e.Property.Equals(MouseEnterCursorProperty))
{
b.MouseEnter -= MouseEnter; // In case the property changes more than once we don't want to hook it more than once
b.MouseEnter += MouseEnter;
}
else if (e.Property.Equals(InitialImageProperty))
{
SetButtonImage(b, GetInitialImage(b));
}
else if (e.Property.Equals(MouseLeftButtonDownProperty))
{
// Had to use Preview because non-Preview never fired
b.PreviewMouseLeftButtonDown -= MouseLeftButtonDown;
b.PreviewMouseLeftButtonDown += MouseLeftButtonDown;
}
else if (e.Property.Equals(MouseLeftButtonUpProperty))
{
// Had to use Preview because non-Preview never fired
b.PreviewMouseLeftButtonUp -= MouseLeftButtonUp;
b.PreviewMouseLeftButtonUp += MouseLeftButtonUp;
}
}
private static void MouseEnter(object sender, MouseEventArgs e)
{
Button b = sender as Button;
if (b == null)
return;
b.Cursor = GetMouseEnterCursor(b);
e.Handled = false;
}
private static void MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Button b = sender as Button;
if (b == null)
return;
SetButtonImage(b, GetMouseLeftButtonDown(b));
e.Handled = false;
}
private static void MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Button b = sender as Button;
if (b == null)
return;
SetButtonImage(b, GetMouseLeftButtonUp(b));
e.Handled = false;
}
private static void SetButtonImage(Button b, Uri uri)
{
if (b == null || uri == null)
return;
var img = b.Content as Image;
if (img == null)
img = new Image();
BitmapImage bm = new BitmapImage();
bm.BeginInit();
bm.UriSource = uri;
bm.EndInit();
img.Source = bm;
b.Content = img;
}
}
}
Screenshots
Initial (mouse was over it to show cursor change but cursor isn't in the image):
LeftMouseButtonDown:
LeftMouseButtonUp:

Animated GIF works in Design view of VS2010, but has a runtime error?

I have been trying to get Mike Eshva's code from his answer about Animated Gifs to work, from here:
How do I get an animated gif to work in WPF?
Restated here (including translated comments):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Media;
using System.Diagnostics;
namespace WpfBrowserApplication1 {
/// <summary>
/// Control of the "Images", which supports animated GIF.
/// </summary>
public class AnimatedImage : Image {
public AnimatedImage() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(AnimatedImage), new FrameworkPropertyMetadata(typeof(AnimatedImage)));
}
#region Public properties
/// <summary>
/// Gets/sets the number of current frame.
/// </summary>
public int FrameIndex {
get { return (int)GetValue(FrameIndexProperty); }
set { SetValue(FrameIndexProperty, value); }
}
/// <summary>
/// Get the BitmapFrame List.
/// </summary>
public List<BitmapFrame> Frames { get; private set; }
/// <summary>
/// Get or set the repeatBehavior of the animation when source is gif formart.This is a dependency object.
/// </summary>
public RepeatBehavior AnimationRepeatBehavior {
get { return (RepeatBehavior)GetValue(AnimationRepeatBehaviorProperty); }
set { SetValue(AnimationRepeatBehaviorProperty, value); }
}
public new BitmapImage Source {
get { return (BitmapImage)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
public Uri UriSource {
get { return (Uri)GetValue(UriSourceProperty); }
set { SetValue(UriSourceProperty, value); }
}
#endregion
#region Protected interface
/// <summary>
/// Provides derived classes an opportunity to handle changes to the Source property.
/// </summary>
protected virtual void OnSourceChanged(DependencyPropertyChangedEventArgs e) {
ClearAnimation();
BitmapImage source;
if (e.NewValue is Uri) {
source = new BitmapImage();
source.BeginInit();
source.UriSource = e.NewValue as Uri;
source.CacheOption = BitmapCacheOption.OnLoad;
source.EndInit();
} else if (e.NewValue is BitmapImage) {
source = e.NewValue as BitmapImage;
} else {
return;
}
BitmapDecoder decoder;
if (source.StreamSource != null) {
decoder = BitmapDecoder.Create(source.StreamSource, BitmapCreateOptions.DelayCreation, BitmapCacheOption.OnLoad);
} else if (source.UriSource != null) {
decoder = BitmapDecoder.Create(source.UriSource, BitmapCreateOptions.DelayCreation, BitmapCacheOption.OnLoad);
} else {
return;
}
if (decoder.Frames.Count == 1) {
base.Source = decoder.Frames[0];
return;
}
this.Frames = decoder.Frames.ToList();
PrepareAnimation();
}
#endregion
#region Private properties
private Int32Animation Animation { get; set; }
private bool IsAnimationWorking { get; set; }
#endregion
#region Private methods
private void ClearAnimation() {
if (Animation != null) {
BeginAnimation(FrameIndexProperty, null);
}
IsAnimationWorking = false;
Animation = null;
this.Frames = null;
}
private void PrepareAnimation() {
Animation =
new Int32Animation(
0,
this.Frames.Count - 1,
new Duration(
new TimeSpan(
0,
0,
0,
this.Frames.Count / 10,
(int)((this.Frames.Count / 10.0 - this.Frames.Count / 10) * 1000)))) {
RepeatBehavior = RepeatBehavior.Forever
};
base.Source = this.Frames[0];
BeginAnimation(FrameIndexProperty, Animation);
IsAnimationWorking = true;
}
private static void ChangingFrameIndex
(DependencyObject dp, DependencyPropertyChangedEventArgs e) {
AnimatedImage animatedImage = dp as AnimatedImage;
if (animatedImage == null || !animatedImage.IsAnimationWorking) {
return;
}
int frameIndex = (int)e.NewValue;
((Image)animatedImage).Source = animatedImage.Frames[frameIndex];
animatedImage.InvalidateVisual();
}
/// <summary>
/// Handles changes to the Source property.
/// </summary>
private static void OnSourceChanged
(DependencyObject dp, DependencyPropertyChangedEventArgs e) {
((AnimatedImage)dp).OnSourceChanged(e);
}
#endregion
#region Dependency Properties
/// <summary>
/// FrameIndex Dependency Property
/// </summary>
public static readonly DependencyProperty FrameIndexProperty =
DependencyProperty.Register(
"FrameIndex",
typeof(int),
typeof(AnimatedImage),
new UIPropertyMetadata(0, ChangingFrameIndex));
/// <summary>
/// Source Dependency Property
/// </summary>
public new static readonly DependencyProperty SourceProperty =
DependencyProperty.Register(
"Source",
typeof(BitmapImage),
typeof(AnimatedImage),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsMeasure,
OnSourceChanged));
/// <summary>
/// AnimationRepeatBehavior Dependency Property
/// </summary>
public static readonly DependencyProperty AnimationRepeatBehaviorProperty =
DependencyProperty.Register(
"AnimationRepeatBehavior",
typeof(RepeatBehavior),
typeof(AnimatedImage),
new PropertyMetadata(null));
public static readonly DependencyProperty UriSourceProperty =
DependencyProperty.Register(
"UriSource",
typeof(Uri),
typeof(AnimatedImage),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsMeasure,
OnSourceChanged));
#endregion
}
}
Here's the XAML for my page just trying to show one animated GIF:
<Page x:Class="WpfBrowserApplication1.Page1"
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:my="clr-namespace:WpfBrowserApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="Page1">
<Grid>
<my:AnimatedImage x:Name="Wait" Source="arrows.gif" Width="16" Height="16" />
</Grid>
</Page>
In the design view of Page1, I see the animated gif happily doing what it's supposed to do, no build errors, no design errors, it just works the way it's supposed to. As soon as I start the page to make sure it works in the browser, I get the following error pointed at the line: <my:AnimatedImage x:Name="Wait" Source="arrows.gif" Width="16" Height="16" />
'Set property 'WpfBrowserApplication1.AnimatedImage.Source' threw an exception.' Line number '11' and line position '21'.
I've been trying to get this to work for awhile. What can I do to get the image to animate when the project runs?
EDIT:
You can download the whole project from my blog (Click on Steve)
This one works fine:
XAML:
<controls:GifImage x:Name="gifImage" Stretch="None" GifSource="/SomeImage.gif" AutoStart="True" />
Note:
If AutoStart is set to false, you need to call either Show() or StartAnimation() manually from your code.
C#:
public class GifImage : Image
{
#region Memmbers
private GifBitmapDecoder _gifDecoder;
private Int32Animation _animation;
private bool _isInitialized;
#endregion Memmbers
#region Properties
private int FrameIndex
{
get { return (int)GetValue(FrameIndexProperty); }
set { SetValue(FrameIndexProperty, value); }
}
private static readonly DependencyProperty FrameIndexProperty =
DependencyProperty.Register("FrameIndex", typeof(int), typeof(GifImage), new FrameworkPropertyMetadata(0, new PropertyChangedCallback(ChangingFrameIndex)));
private static void ChangingFrameIndex(DependencyObject obj, DependencyPropertyChangedEventArgs ev)
{
GifImage image = obj as GifImage;
image.Source = image._gifDecoder.Frames[(int)ev.NewValue];
}
/// <summary>
/// Defines whether the animation starts on it's own
/// </summary>
public bool AutoStart
{
get { return (bool)GetValue(AutoStartProperty); }
set { SetValue(AutoStartProperty, value); }
}
public static readonly DependencyProperty AutoStartProperty =
DependencyProperty.Register("AutoStart", typeof(bool), typeof(GifImage), new UIPropertyMetadata(false, AutoStartPropertyChanged));
private static void AutoStartPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
(sender as GifImage).StartAnimation();
}
public string GifSource
{
get { return (string)GetValue(GifSourceProperty); }
set { SetValue(GifSourceProperty, value); }
}
public static readonly DependencyProperty GifSourceProperty =
DependencyProperty.Register("GifSource", typeof(string), typeof(GifImage), new UIPropertyMetadata(string.Empty, GifSourcePropertyChanged));
private static void GifSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
// CARLO 20100622: Reinitialize animation everytime image is changed
(sender as GifImage).Initialize();
}
#endregion Properties
#region Private Instance Methods
private void Initialize()
{
_gifDecoder = new GifBitmapDecoder(new Uri("pack://application:,,," + this.GifSource), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
_animation = new Int32Animation(0, _gifDecoder.Frames.Count - 1, new Duration(new TimeSpan(0, 0, 0, _gifDecoder.Frames.Count / 10, (int)((_gifDecoder.Frames.Count / 10.0 - _gifDecoder.Frames.Count / 10) * 1000))));
_animation.RepeatBehavior = RepeatBehavior.Forever;
this.Source = _gifDecoder.Frames[0];
_isInitialized = true;
}
#endregion Private Instance Methods
#region Public Instance Methods
/// <summary>
/// Shows and starts the gif animation
/// </summary>
public void Show()
{
this.Visibility = Visibility.Visible;
this.StartAnimation();
}
/// <summary>
/// Hides and stops the gif animation
/// </summary>
public void Hide()
{
this.Visibility = Visibility.Collapsed;
this.StopAnimation();
}
/// <summary>
/// Starts the animation
/// </summary>
public void StartAnimation()
{
if (!_isInitialized)
this.Initialize();
BeginAnimation(FrameIndexProperty, _animation);
}
/// <summary>
/// Stops the animation
/// </summary>
public void StopAnimation()
{
BeginAnimation(FrameIndexProperty, null);
}
#endregion Public Instance Methods
}

Resources