0

I have a UserControl Library containing a user control to make a custom title bar for windows. I have given this title bar two dependency properties - Title and Icon, which I would like to be able to set when I use the control through a DLL.

I have got the title working correctly, so that when I set a Title property of the Window's ViewModel, it is populated into the title bar. However, I cannot get the image to do the same.

I have tried all sorts of variations, and read an entire course on using dependency properties, but I just can't seem to get the image to show. I have tried using ImageSource properties, Project Resources, and Images, but all I get is a blank. Can you point me in the right direction to fix the issue?

Here is the xaml for the usercontrol (I Read that I had to use the TemplatedParent RelativeSource for the DP, so that is put in - I believe that this will refer to the MainViewModel in the implementation):

<UserControl x:Class="WPFLibrary.User_Controls.TitleBar"
             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">
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/WPFLibrary;component/Styles/Buttons.xaml"/>
                <ResourceDictionary Source="pack://application:,,,/WPFLibrary;component/Styles/Gradients.xaml"/>
                <ResourceDictionary Source="pack://application:,,,/WPFLibrary;component/Styles/Text.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>
    <DockPanel VerticalAlignment="Top" Grid.Row="0" Grid.ColumnSpan="3"
               MouseDown="TitleBar_MouseDown"
               DataContext="{Binding}"
               Background="{StaticResource WindowTitleBar}">
        <Grid Grid.Row="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="5"/>
                <ColumnDefinition Width="30"/>
                <ColumnDefinition Width="5"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="90"/>
            </Grid.ColumnDefinitions>
            <Image Grid.Column="1" Source="{Binding Icon,RelativeSource={RelativeSource TemplatedParent}}"/>
            <TextBlock x:Name="Title_Bar"
                       Grid.Column="3"
                       Text="{Binding Title}"
                       Style="{StaticResource Text_WindowTitle}">
            </TextBlock>
            <!--StackPanel Containing the buttons excluded for SO Question-->
        </Grid>
    </DockPanel>
</UserControl>

and here is the code behind for the same control (Following from some other SO answers, I have tried using project resources and ImageSource for the Image property, but to no avail):

using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace WPFLibrary.User_Controls
{
    /// <summary>
    /// Interaction logic for TitleBar.xaml
    /// </summary>
    public partial class TitleBar : UserControl, INotifyPropertyChanged
    {

        public Image Icon
        {
            get { return (Image)GetValue(IconProperty); }
            set { SetValue(IconProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Icon.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(Image), typeof(TitleBar) );

        private static PropertyChangedEventArgs _stateChange = new PropertyChangedEventArgs("Windowstate");
        public WindowState _windowState;

        public WindowState Windowstate
        {
            get
            { return _windowState; }
            set
            {
                _windowState = value;
                NotifyChange(_stateChange);
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyChange(PropertyChangedEventArgs e)
        {
            PropertyChanged?.Invoke(this, e);
        }

        public TitleBar()
        {
            InitializeComponent();
        }

        //Button Click Methods excluded for SO question
    }
}

The xaml for the implementation of the usercontrol is like this - I set the binding to the MainViewModel; that has a constructor that sets the properties.

<Window x:Class="Demo_of_WPF_Library.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:uc="clr-namespace:WPFLibrary.User_Controls;assembly=WPFLibrary"
        xmlns:vm="clr-namespace:Demo_of_WPF_Library.ViewModel"
        mc:Ignorable="d"
        WindowStyle="None"
        BorderBrush="{StaticResource WindowBackgroundBrush}"
        x:Name="Main_Window"
        WindowState="{Binding Windowstate,ElementName=Titlebar}"
        Background="{StaticResource WindowBackgroundBrush}"
        Height="300" Width="800">
    <Window.DataContext>
        <vm:MainView_Model/>
    </Window.DataContext>
    <Grid>
        <uc:TitleBar Name="Titlebar" Icon="{Binding Icon}">
        </uc:TitleBar>
    </Grid>
</Window>

Here is the code for the MainView_Model that holds the properties for the mainwindow:

namespace Demo_of_WPF_Library.ViewModel
{
    public class MainView_Model
    {
        private string _title;
        public string Title
        {
            get { return _title; }
            set { _title = value; }
        }
        private Image _icon;

        public Image Icon
        {
            get { return _icon; }
            set { _icon = value; }
        }

        public MainView_Model()
        {
            Title = "Demo WPF Library";
            Icon = new Image();
            Icon.Source = new BitmapImage(new Uri("/View/Images/IsItOk.ico",UriKind.Relative));
        }
    }
}

Finally, here is the code behind the mainwindow, which sets the binding to the MainViewModel and initialises the window:

public partial class MainWindow : Window
{
    public MainView_Model MainView { get; set; }
    public MainWindow()
    {
        MainView = new MainView_Model();
        DataContext = MainView;
        InitializeComponent();
    }
}

I honestly cannot see what it is that is missing / wrong here and I have read loads of articles, questions and answers that basically seem to say that I'm doing things in the correct way... So what on earth is wrong?

High Plains Grifter
  • 1,357
  • 1
  • 12
  • 36
  • I"m probably missing where you actually set the Icon property. ALSO depending where then you're MainView_Model class needs to implement INofityPropertyChanged I see it now. When you step through it, is Icon.Source set? – kenny Apr 29 '19 at 10:58
  • I thought I was setting the Icon property In the constructor for the mainView_model... setting the Icon.Source is not setting the image? I added in the INotifyPropertyChange implementation, but that has not solved the issue. – High Plains Grifter Apr 29 '19 at 11:01
  • Did you step through and ensure that .Source is set? Does this help? https://stackoverflow.com/a/12693661/3225 – kenny Apr 29 '19 at 11:11
  • `.Source` is set, although all other properties are default or empty. The Image is a `System.Windows.Controls.Image`, by the way, not a `System.Drawing.Image`. This caused me a deal of angst, so I figured I would mention! – High Plains Grifter Apr 29 '19 at 11:15
  • I read a different SO answer and set the build action to `Component`! I have tried both ways and neither solves the issue. – High Plains Grifter Apr 29 '19 at 11:20
  • I think the new Image() probably requires arguments, likely that's the way to pass in the image path which might set all of them. – kenny Apr 29 '19 at 11:21

2 Answers2

4

Your binding is wrong. You shouldn't use a TemplatedParent if you have no template. Use:

xmlns:local="clr-namespace:WPFLibrary.User_Controls"
<Image Grid.Column="1" Source="{Binding Icon, RelativeSource={RelativeSource AncestorType=local:TitleBar}}"/>
Rekshino
  • 6,954
  • 2
  • 19
  • 44
3

The problem is you've got an Image in your XAML which is trying to bind to an Image in your view model. GUI objects like Image have no place in your view model layer, change your Icon dependency property to be of type ImageSource and bind your XAML image to that instead.

Mark Feldman
  • 15,731
  • 3
  • 31
  • 58
  • I'm not certain I understand exactly how to implement that - I changed the type of the DP in the UserControl to ImageSource and the same in the view model, setting the source using a BitmapImage as before, but that did not work. I also added an image to the xaml as a static resource and bound the new ImageSource DP to that, but it also failed. I'm sorry, I think I have got myself considerably muddled. Could you give a little more detail, please? – High Plains Grifter Apr 29 '19 at 12:01
  • I've tried some more things and now I am certain I don't understand how to implement what you describe correctly – High Plains Grifter Apr 29 '19 at 14:39
  • Got it! Combined with @Rekshino 's answer, this solved it - I put the reference in MainWindow.xaml.cs, instead of the view model and used it to directly set `Titlebar.Icon = bitmap`. I wish I could mark two responses as the answer! – High Plains Grifter Apr 29 '19 at 15:17