Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binding the Window Title to properties using a converter

I'm trying to bind the window Title attribute so that it will show the filename and modified status for an object. The filename and modified status are both dependency properties on the object.

I know I could probably just add a "WindowTitle" property or such to the object, but that seems rather hacky. I've created a very stripped down version of what I'm trying to do.

Here's the XAML:

<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="{Binding Converter={StaticResource windowTitleConverter}}" Height="195" Width="245">
<Window.Resources>
    <local:WindowTitleConverter x:Key="windowTitleConverter"/>
</Window.Resources>
<Grid Height="150" Width="217">
    <TextBox Height="23" HorizontalAlignment="Left" Margin="12,12,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" Text="{Binding FileName}" />
    <CheckBox Content="Modified" Height="16" HorizontalAlignment="Left" Margin="12,41,0,0" Name="checkBox1" VerticalAlignment="Top" IsChecked="{Binding Modified}" />
</Grid>

And the Code:

using System;
using System.Globalization;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new Foo();
        }
    }

    public class Foo : DependencyObject
    {
        public string FileName
        {
            get { return (string)GetValue(FileNameProperty); }
            set { SetValue(FileNameProperty, value); }
        }

        public static readonly DependencyProperty FileNameProperty =
            DependencyProperty.Register("FileName", typeof(string), typeof(Foo), new UIPropertyMetadata());

        public bool Modified
        {
            get { return (bool)GetValue(ModifiedProperty); }
            set { SetValue(ModifiedProperty, value); }
        }

        public static readonly DependencyProperty ModifiedProperty =
            DependencyProperty.Register("Modified", typeof(bool), typeof(Foo), new UIPropertyMetadata(0));
    }

    public class WindowTitleConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            Foo foo = (Foo)value;
            if (foo == null || foo.FileName == null)
                return "Foo";
            return foo.FileName + (foo.Modified ? " *" : "");
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }
}
like image 660
Rob W. Avatar asked Mar 24 '11 16:03

Rob W.


1 Answers

If you move the Title binding below the Resources it will work. I'm not sure why the order of declaration is of importance here but it is, seems like a bug to me

<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"
        Height="195" Width="245">
    <Window.Resources>
        <local:WindowTitleConverter x:Key="windowTitleConverter"/>
    </Window.Resources>
    <Window.Title>
        <Binding Converter="{StaticResource windowTitleConverter}"/>
    </Window.Title>
    <!--...-->
</Window>

Update

The problem you're getting now is because the Dependency Property Modified has the wrong default value type. It is of type bool and you set it to 0 so change it to false and it should work

public static readonly DependencyProperty ModifiedProperty =
    DependencyProperty.Register("Modified",
                                typeof(bool),
                                typeof(Foo),
                                new UIPropertyMetadata(false));

Update

I'm not aware of any way to raise PropertyChanged when binding directly to the DataContext. A little workaround you could use is to bind to a property called This which simply returns this

<Window.Title>
    <Binding Path="This" Converter="{StaticResource windowTitleConverter}"/>
</Window.Title>

Then you can use the PropertyChangedCallback to raise PropertyChanged for This

public class Foo : DependencyObject, INotifyPropertyChanged
{
    public Object This
    {
        get { return this; }
    }
    public bool Modified
    {
        get { return (bool)GetValue(ModifiedProperty); }
        set { SetValue(ModifiedProperty, value); }
    }
    public string FileName
    {
        get { return (string)GetValue(FileNameProperty); }
        set { SetValue(FileNameProperty, value); }
    }
    public static readonly DependencyProperty FileNameProperty =
        DependencyProperty.Register("FileName",
                                    typeof(string),
                                    typeof(Foo),
                                    new UIPropertyMetadata(string.Empty, new PropertyChangedCallback(OnFileNameChanged)));

    public static readonly DependencyProperty ModifiedProperty =
        DependencyProperty.Register("Modified",
                                    typeof(bool),
                                    typeof(Foo),
                                    new UIPropertyMetadata(false, new PropertyChangedCallback(OnModifiedChanged)));

    private static void OnFileNameChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        Foo foo = obj as Foo;
        foo.OnPropertyChanged("This");
    }
    private static void OnModifiedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        Foo foo = obj as Foo;
        foo.OnPropertyChanged("This");
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Another solution would be to use a MultiBinding instead which would remove the need for the This property

<Window.Resources>
    <local:TitleMultiConverter x:Key="TitleMultiConverter"/>
</Window.Resources>
<Window.Title>
    <MultiBinding Converter="{StaticResource TitleMultiConverter}">
        <Binding Path="FileName"/>
        <Binding Path="Modified"/>
    </MultiBinding>
</Window.Title>

TitleMultiConverter

public class TitleMultiConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        string fileName = values[0].ToString();
        bool modified = (bool)values[1];
        if (fileName == null)
            return "Foo";
        return fileName + (modified ? " *" : "");
    }
    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
like image 157
Fredrik Hedblad Avatar answered Nov 18 '22 08:11

Fredrik Hedblad