views:

29

answers:

1

I have a button that loads XAML from a file, creates a control from it and adds this control as a child to a canvas that is part of a template present in the resources of a dockpanel on the window. The window also has a combobox named cboTColour and a combobox named cboBColour which I use to set a simple gradient background on my loaded control.

I load the XAML and add it to my canvas using the following code:

XmlReader xaml = XmlReader.Create(filename);
newControl = (Viewbox)XamlReader.Load(xaml);
((Canvas)(testButton.Template.FindName("MyCanvas", testButton))).Children.Clear();
((Canvas)(testButton.Template.FindName("MyCanvas", testButton))).Children.Add(newControl);

And here is the XAML I load:

<?xml version="1.0" encoding="utf-8"?>
<Viewbox xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="Document" Stretch="Fill">
    <Canvas Height="64" Width="128" ClipToBounds="True">
        <Canvas.Background>
            <!--Horizontal Gradient-->
            <LinearGradientBrush StartPoint="1,0">
                <GradientStop Color="{Binding ElementName=cboTColour, Path=SelectedItem.Name}" Offset="0"></GradientStop>
                <GradientStop Color="{Binding ElementName=cboBColour, Path=SelectedItem.Name}" Offset="1"></GradientStop>
            </LinearGradientBrush>
        </Canvas.Background>
    </Canvas>
</Viewbox>

I have tried putting the XAML straight into the designer and it works perfectly so it is not an issue with that. When I load the XAML from file, the controls are being created and placed correctly, but the databinding does not work - the colours do not change. I get the following errors:

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=cboTColour'. BindingExpression:Path=SelectedItem.Name; DataItem=null; target element is 'GradientStop' (HashCode=24393646); target property is 'Color' (type 'Color')
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=cboBColour'. BindingExpression:Path=SelectedItem.Name; DataItem=null; target element is 'GradientStop' (HashCode=23972246); target property is 'Color' (type 'Color')

I assume what's happening is when the XAMLReader loads the xaml ad creates a control from it, it is not sure of the path to my combo-boxes, as the xaml is not yet part of the window, and when the control is added to the window, this binding doesn't update, but I do not know how to either modify my bindings in the XAML to reflect where my combo-boxes would be situated in relation to it or to modify the XAMLReader or overall datacontext to take into account my controls. I can also assure you that the comboboxes have been created by this point as the code runs when a button is pressed on the window the comboboxes are in.

I MUST specify that I CANNOT modify the bindings themselves in the code, as the bindings will appear in various places and at various times throughout the different XAML files I will load.

Any help would be much appreciated.

+1  A: 

It looks like XamlReader put restrictions on the xaml it loads so it can not contain unknown element names not defined in the loaded xml. Instead use data binding to properties which works fine but requires some code behind. Below code will change the background on the loaded control when user changes the selected item of the comboboxes. You need to add a value converter that converts the colors to human readable names to be used by the comboboxes.

XAML:

<StackPanel>
    <ComboBox 
        ItemsSource="{Binding Path=AvailableColors}" 
        SelectedValuePath="Color"
        SelectedValue="{Binding Path=SelectedColors[color0].Color}" />
    <ComboBox 
        ItemsSource="{Binding Path=AvailableColors}" 
        SelectedValuePath="Color"
        SelectedValue="{Binding Path=SelectedColors[color1].Color}" />
    <ContentControl Name="newControl" />
</StackPanel>

Code behind:

using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Media;
using System.Xml;

namespace LoadTest
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();

            SelectedColors = new Dictionary<string, ColorInfo>();
            SelectedColors.Add("color0", AvailableColors.First());
            SelectedColors.Add("color1", AvailableColors.Last());

            XmlReader xaml = XmlReader.Create("newcontrol.xml");
            newControl.Content = XamlReader.Load(xaml);
            newControl.DataContext = SelectedColors;

            DataContext = this;
        }

        public List<ColorInfo> AvailableColors
        {
            get
            {
                return new List<ColorInfo>()
                {
                    new ColorInfo() {Color = Colors.Red },
                    new ColorInfo() { Color = Colors.Green },
                    new ColorInfo() { Color = Colors.Blue },
                    new ColorInfo() { Color = Colors.Yellow }
                };
            }
        }

        public Dictionary<string, ColorInfo> SelectedColors { get; private set;}
    }

    public class ColorInfo : INotifyPropertyChanged
    {
        private Color _color;
        public Color Color
        {
            get { return _color; }
            set 
            {
                _color = value;
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("Color"));
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}

XAML file to load (newcontrol.xml):

<?xml version="1.0" encoding="utf-8"?>
<Viewbox
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  x:Name="Document" Stretch="Fill">
  <Canvas Height="64" Width="128" ClipToBounds="True">
    <Canvas.Background>
      <LinearGradientBrush StartPoint="1,0">
        <GradientStop Color="{Binding Path=[color0].Color}" Offset="0" />
        <GradientStop Color="{Binding Path=[color1].Color}" Offset="1" />
      </LinearGradientBrush>
    </Canvas.Background>
  </Canvas>
</Viewbox>
Wallstreet Programmer
That worked brilliantly, thanks.
Nick Udell