3

I want to create an application that does stuff in the background and is controllable via a Tray icon. This tray icon has a context menu with a checkbox that can be set to Enabled, and then the background task starts.

I am using WPF and the Hardcodet WPF NotifyIcon.

Here is my MainWindow.xaml:

<Window x:Name="mainWindow" x:Class="MyTrayApplication.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:MyTrayApplication"
        xmlns:tb="http://www.hardcodet.net/taskbar"
        mc:Ignorable="d"
        Title="MainWindow" Height="10" Width="10" Visibility="Hidden">

    <tb:TaskbarIcon x:Name="taskbarIcon" IconSource="MyTrayApplication.ico" ToolTipText="My tray application" >
        <tb:TaskbarIcon.ContextMenu>
            <ContextMenu>
                <MenuItem x:Name="enabledItem" Header="Enabled" IsCheckable="True" IsChecked="{Binding Path=Enabled, ElementName=mainWindow, UpdateSourceTrigger=PropertyChanged}"/>
                <MenuItem x:Name="configureItem" Header="Configure..." Click="configureItem_Click"/>
                <MenuItem x:Name="exitItem" Header="Exit" Click="exitItem_Click"/>
            </ContextMenu>
        </tb:TaskbarIcon.ContextMenu>
    </tb:TaskbarIcon>

</Window>

Here is my Code Behind:

using System;
using System.Windows;

namespace MyTrayApplication {
    /// <summary>
    /// Interaktionslogik für MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window {
        private bool _enabled;

        public MainWindow() {
            InitializeComponent();
        }

        private void exitItem_Click(object sender, RoutedEventArgs e) {
            Close();
        }

        private void configureItem_Click(object sender, RoutedEventArgs e) {
            // Code to fire up configuration
        }

        public bool Enabled {
            get {
                return _enabled;
            }
            set {
                _enabled = value;
                Console.WriteLine(value);
                // Code to enable the background task
            }
        }
    }
}

This does not work. I get the following in the output when running it:

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=mainWindow'. BindingExpression:Path=Enabled; DataItem=null; target element is 'MenuItem' (Name='enabledItem'); target property is 'IsChecked' (type 'Boolean')

The strange thing is: mainWindow IS found in the XAML editor when I type ElementName= for the IsChecked binding, as IntelliSense shows it to me.

What DOES work though is setting the DataContext programmatically in the Code-Behind right after InitializeComponent();:

DataContext = this;

And then changing the binding to:

IsChecked="{Binding Enabled, UpdateSourceTrigger=PropertyChanged}"

This does output the following:

System.Windows.Data Error: 40 : BindingExpression path error: 'Enabled' property not found on 'object' ''TaskbarIcon' (Name='taskbarIcon')'. BindingExpression:Path=Enabled; DataItem='TaskbarIcon' (Name='taskbarIcon'); target element is 'MenuItem' (Name='enabledItem'); target property is 'IsChecked' (type 'Boolean')

but it works.

What am I doing wrong here? Or is setting the DataContext programmatically the way to go?

rabejens
  • 7,594
  • 11
  • 56
  • 104
  • Maybe you have to change it to "IsEnabled" property. – Babbillumpa Jul 01 '16 at 07:40
  • No, I want to bind IsChecked, but I figured it out. I have to set `DataContext="{Binding RelativeSource={RelativeSource Self}}"` in the opening `` tag and use the second, shorter binding. – rabejens Jul 01 '16 at 07:41
  • Sorry didn't read very well your question. I misunderstood. – Babbillumpa Jul 01 '16 at 07:44
  • It looks like that, in the second try you did, The DataContext of "enabledItem" is set in a first moment to TaskbarIcon and not to the mainwindow – Babbillumpa Jul 01 '16 at 07:54
  • Yes, this was the case. Strangely enough, the `FindAncestor` method didn't work either, but the method I told about in my other comment works. – rabejens Jul 01 '16 at 08:10

3 Answers3

6

You are dealing with ContextMenu. This is one of the trickiest controls to deal with, because it is actually a Window on its own, which has its own visual tree. You have to imagine the underlying is doing something similar to this:

Window contextMenu = new Window();
contextMenu.Show();

This window instance obviously can't directly bind to the MainWindow that creates it, can it?

Binding

ContextMenu is a separate entity on its own. By default its DataContext is isolated from the parent (placement target). Luckily, ContextMenu has PlacementTarget property which is automatically the object which you put it in in XAML.

<SomeControl>
    <SomeControl.ContextMenu>
        <ContextMenu ....>
        <!-- This ContextMenu's PlacementTarget is automatically SomeControl -->
        <ContextMenu>
    </SomeControl.ContextMenu>
</SomeControl>

In order to do binding, you need to do this:

<ContextMenu DataContext="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.DataContext}" ...>

For your case, you are using some external API. Apparently this custom control has already set the DataContext to be the DataContext of its placement target.

Your problem is because your window's DataContext is not set to itself, which you have solved. Either xaml or code-behind works equally well.

ElementName

When you use ElementName, the binding engine will generally ignore DataContext. It will start to look for the element with the specific name in its own visual tree. Like what I initially said, ContextMenu has its own visual tree, so obviously it wouldn't be able to find that element in its visual tree.

Jai
  • 8,165
  • 2
  • 21
  • 52
2

This is a little tricky. The problem is that ContextMenu is not a part of visual tree. See this post for a solution: WPF: Binding a ContextMenu to an MVVM Command

Community
  • 1
  • 1
michauzo
  • 356
  • 1
  • 9
0

Element Names are case sensitive, you should update the it to "MainWindow" from "mainWindow" because the binding cannot find the element as the error suggests

Shivani Katukota
  • 859
  • 9
  • 16