How do I implement a DependencyProperty
on a custom wpf control for an ImageSource
?
I created a custom control (button) which amongst other things does display an image. I want to be able to set the ImageSource for the image from the outside of the control, so I implemented a DependencyProperty. Whenever I try to change the ImageSource though, I am getting a SystemInvalidOperationException
: "The calling thread cannot access this object because a different thread owns it."
OK, so the main thread cannot access the image control, so I need to use the Dispatcher - but where and how? Apparently the exception is thrown in the setter, executing SetValue(ImageProperty, value);
tv_CallStart.xaml:
<Button x:Class="EHS_TAPI_Client.Controls.tv_CallStart" x:Name="CallButton"
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"
mc:Ignorable="d"
d:DesignHeight="24" d:DesignWidth="24" Padding="0" BorderThickness="0"
Background="#00000000" BorderBrush="#FF2467FF" UseLayoutRounding="True">
<Image x:Name="myImage"
Source="{Binding ElementName=CallButton, Path=CallImage}" />
</Button>
tv_CallStart.xaml.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace EHS_TAPI_Client.Controls
{
public partial class InitiateCallButton : Button
{
public ImageSource CallImage
{
get { return (ImageSource)GetValue(CallImageProperty); }
set { SetValue(CallImageProperty, value); }
}
public static readonly DependencyProperty CallImageProperty =
DependencyProperty.Register("CallImage", typeof(ImageSource), typeof(InitiateCallButton), new UIPropertyMetadata(null));
public InitiateCallButton()
{
InitializeComponent();
}
}
}
setting the image from the code-behind of the UI-thread:
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.UriSource = new Uri("pack://application:,,,/EHS-TAPI-Client;component/Images/call-start.png");
bi.EndInit();
bi.Freeze();
CallButton.CallImage = bi;
and the MainWindow.xaml where the control is initialised:
<ctl:InitiateCallButton x:Name="CallButton" CallImage="/Images/call-start.png" />
Adapted the above source code to reflect my progress..
Solution:
The code posted above is working fine. The important change from the initial version is the addition of the Freeze() method from the UI thread (see accepted answer). The actual problem in my project is not the button not being initialised in the UI thread, but the new image being set from another thread. The image is set in an event handler which itself is triggered from another thread. I resolved the problem by using the Dispatcher
:
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.UriSource = new Uri("pack://application:,,,/EHS-TAPI-Client;component/Images/call-start.png");
bi.EndInit();
bi.Freeze();
if (!Application.Current.Dispatcher.CheckAccess())
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)delegate()
{
CallButton.CallImage = bi;
});
}
else
{
CallButton.CallImage = bi;
}