I have 2 user controls in MainWindow.xaml, They are SidePannel and Description. I am passing an DictionaryModel Object. In the DictionaryModel object there is a Collection of Words and Another object called SidePannelModel. In the SidePannelModel there is another collection of Words and a string property.
DictionaryModel
public class DictionaryModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public SidePannelModel SideModel { get; set; }
public IEnumerable<WordModel> Language2 { get; set; }
public void ToggleLanguage()
{
var temp = this.Language2;
this.Language2 = this.SideModel.Language1;
this.SideModel.Language1 = temp;
OnPropertyChanged("DictionaryChanged");
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
SidePannelModel
public class SidePannelModel
{
public IEnumerable<WordModel> Language1 { get; set; }
public string SearchWord { get; set; }
}
I am passing the DictionaryModel to DataContext in MainWindow.xaml.cs.
public partial class MainWindow : Window
{
private DictionaryModel dictionary;
public MainWindow()
{
InitializeComponent();
dictionary = new DictionaryModel
{
SideModel = new SidePannelModel {
SearchWord=null,
Language1 = new List<WordModel>()
{
new WordModel() { Word = "Test1" }
, new WordModel() { Word = "Test2" }
, new WordModel() { Word = "Test3" }
, new WordModel() { Word = "Test4" }
, new WordModel() { Word = "Test5" }
, new WordModel() { Word = "Test6" }
, new WordModel() { Word = "Test7" }
, new WordModel() { Word = "Test8" }
, new WordModel() { Word = "Test9" }
, new WordModel() { Word = "Test10" }
} as IEnumerable<WordModel>
},
Language2 = new List<WordModel>()
{
new WordModel() { Word = "Test1" }
, new WordModel() { Word = "Test2" }
, new WordModel() { Word = "Test3" }
, new WordModel() { Word = "Test4" }
, new WordModel() { Word = "Test5" }
, new WordModel() { Word = "kkkkk" }
, new WordModel() { Word = "Test7" }
, new WordModel() { Word = "Test8" }
, new WordModel() { Word = "Test9" }
, new WordModel() { Word = "Test10" }
} as IEnumerable<WordModel>,
};
this.DataContext = dictionary;
}
}
This is how I pass the Collection of Words and the SidePannelModel to the user controllers in the ManiWindow.xaml
<Window x:Class="ThaiDictionary.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sidePanel="clr-namespace:ThaiDictionary"
Title="MainWindow"
Height="619">
<Window.Resources>
<Style TargetType="{x:Type ToggleButton}"
x:Key="toggleButtonStyle">
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Content" Value="Thai to English" />
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Content" Value="English to Thai" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid Margin="0,0,2,0">
<DockPanel LastChildFill="True">
<Menu IsMainMenu="True" DockPanel.Dock="Top">
<MenuItem Header="_File" />
<MenuItem Header="_Edit" />
<MenuItem Header="_View" />
<MenuItem Header="_Window" />
<MenuItem Header="_Help" />
</Menu>
<StatusBar Height="22" DockPanel.Dock="Bottom"/>
<sidePanel:SidePanel SideModel="{Binding SidePModel, Mode=TwoWay}" DockPanel.Dock="Left" MinWidth="200" MinHeight="540" Margin="5,5,5,1" Width="196"/>
<DockPanel LastChildFill="True" DockPanel.Dock="Left">
<ToggleButton IsChecked="False" DockPanel.Dock="Top" HorizontalAlignment="Left" Height="30" Width="150" Style="{StaticResource toggleButtonStyle}" Checked="ToggleButton_Checked" Unchecked="ToggleButton_Unchecked">
</ToggleButton>
<sidePanel:Description DockPanel.Dock="Top" WordsList2="{Binding Language2, Mode=TwoWay}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</DockPanel>
</DockPanel>
</Grid>
SideModel is defined in the SidePannel.xaml.cs.
public partial class SidePanel : UserControl
{
public static DependencyProperty SideModelProperty;
static SidePanel()
{
SideModelProperty = DependencyProperty.Register("SideModel", typeof(SidePannelModel), typeof(SidePanel));
}
public SidePanel()
{
InitializeComponent();
}
public SidePannelModel SideModel
{
get
{
return (SidePannelModel)GetValue(SideModelProperty);
}
set
{
SetValue(SideModelProperty, value);
}
}
}
But the SidePanel controller is not loaded with the Words I am expecting.
appropriate part of SidePanel.xaml is given below.
<UserControl x:Class="ThaiDictionary.SidePanel"
x:Name="SidePannelController"
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"
xmlns:l="clr-namespace:ThaiDictionary"
mc:Ignorable="d" MinHeight="300" MinWidth="300" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Background="Azure">
<UserControl.Resources>
</UserControl.Resources>
<DockPanel LastChildFill="True" DataContext="{Binding SidePModel}">
<l:SearchTextBox SearchEventTimeDelay="00:00:02.00" Text="{Binding ElementName= SidePannelController, Path= SearchWord, Mode=TwoWay}" DockPanel.Dock="Top" Search="SearchTextBox_Search" HorizontalAlignment="Stretch" Background="Bisque"/>
<ListBox Name="LeftSidePnel1" ItemsSource="{Binding ElementName= SidePannelController, Path= Language1, Mode=TwoWay}" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Visible" ItemTemplate="{DynamicResource WordTemplate}" MinHeight="266" Height="auto" >
<ListBox.Resources>
<DataTemplate x:Key="WordTemplate">
<Label Content="{Binding Word}" HorizontalAlignment="Left" VerticalAlignment="Top" Width="auto"/>
</DataTemplate>
</ListBox.Resources>
</ListBox>
</DockPanel>
But when I run the project I can not see the words loaded in to the Sidepanel usercontrol. What is wrong with this code. I can not find a way out here. Can you give me some quick help.
Edit:
I have followed the naming conventions and changed the SidePannel.xaml.cs.
Binding error:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=SidePanelController'. BindingExpression:Path=SearchWord; DataItem=null; target element is 'SearchTextBox' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=SidePanelController'. BindingExpression:Path=Language1; DataItem=null; target element is 'ListBox' (Name='LeftSidePnel1'); target property is 'ItemsSource' (type 'IEnumerable')
I have added the full SidePanel.xaml and I have changed the ElementName to SidePannelController as in the xaml file. But it still not getting loading the words
New Edit:
System.Windows.Data Information: 41 : BindingExpression path error: 'SearchWord' property not found for 'object' because data item is null. This could happen because the data provider has not produced any data yet. BindingExpression:Path=SideModel.SearchWord; DataItem='SidePanel' (Name='SidePannelController'); target element is 'SearchTextBox' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Information: 20 : BindingExpression cannot retrieve value due to missing information. BindingExpression:Path=SideModel.SearchWord; DataItem='SidePanel' (Name='SidePannelController'); target element is 'SearchTextBox' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Information: 21 : BindingExpression cannot retrieve value from null data item. This could happen when binding is detached or when binding to a Nullable type that has no value. BindingExpression:Path=SideModel.SearchWord; DataItem='SidePanel' (Name='SidePannelController'); target element is 'SearchTextBox' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=SideModel.SearchWord; DataItem='SidePanel' (Name='SidePannelController'); target element is 'SearchTextBox' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Information: 41 : BindingExpression path error: 'Language1' property not found for 'object' because data item is null. This could happen because the data provider has not produced any data yet. BindingExpression:Path=SideModel.Language1; DataItem='SidePanel' (Name='SidePannelController'); target element is 'ListBox' (Name='LeftSidePnel1'); target property is 'ItemsSource' (type 'IEnumerable')
System.Windows.Data Information: 20 : BindingExpression cannot retrieve value due to missing information. BindingExpression:Path=SideModel.Language1; DataItem='SidePanel' (Name='SidePannelController'); target element is 'ListBox' (Name='LeftSidePnel1'); target property is 'ItemsSource' (type 'IEnumerable')
System.Windows.Data Information: 21 : BindingExpression cannot retrieve value from null data item. This could happen when binding is detached or when binding to a Nullable type that has no value. BindingExpression:Path=SideModel.Language1; DataItem='SidePanel' (Name='SidePannelController'); target element is 'ListBox' (Name='LeftSidePnel1'); target property is 'ItemsSource' (type 'IEnumerable')
Thanks
Language1 and SearchWord are not properties on your UserControl, they're properties on your UserControl.SideModel property.
So change your bindings to include SideModel in your Path property, so it correctly binds to SidePannelController.SideModel.Language1 (or SearchWord), like this:
ItemsSource="{Binding ElementName=SidePannelController, Path=SideModel.Language1}"
But I suspect you're not showing your actual XAML, because your binding error says "Cannot find source for binding", which means it can't find an element named SidePannelController in the VisualTree (since ElementName specifies the source to use for the binding).
If that's the case, you can try using a RelativeSource binding to find the UserControl instead of ElementName
ItemsSource="{Binding Path=SideModel.Language1,
RelativeSource={RelativeSource AncestorType={x:Type l:SidePanel}}"
I don't know what DictionaryModel is.
Maybe a typo, but it's possible you have an extra }, in MainWindow.xaml.cs right before:
Language2 = new List<WordModel>()
That would mean that Language2 is not a member of dictionary.SideModel, but instead is another, separate element of the collection (a sibling property of SideModel?)
However, in MainWindow.xaml, you are Binding to Language2 inside of a binding to SideModel. Since Language2 isn't a child of SideModel, XAML can't resolve the binding.
Related
What I want is to binding the Image source to the ImageData property of MyViewModel instance. For some reason, I cannot set MyWindow.DataContext as MyViewModel instance (it is leagacy code and it will cost too much to do this).
The code snippet is as below and there will be the following error
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=myButton'. BindingExpression:Path=Tag; DataItem=null; target element is 'Image' (Name=''); target property is 'Source' (type 'ImageSource')
// This is the view model class
class MyViewModel : INotifyPropertyChanged
{
public string Name {get ... ;set ... ; }
public string ImageData { ... }
}
// this is the window class
class MyWindow
{
private MyViewModel _ViewModel = new MyViewModel();
public MyViewModel ViewModel
{ get { return _ViewModel; } }
. . .
}
MyWindow.Xaml
<devComponent:ButtonDropDown x:Name="myButton"
Tooltip="{Binding RelativeSource={RelativeSource AncestorType={x:Type mf:MyWindow}}, Path=ViewModel.Name}"
Tag="{Binding RelativeSource={RelativeSource AncestorType={x:Type mf:MyWindow}}, Path=ViewModel.ImageData}"
...>
<devComponent:ButtonDropDown.Image>
<Image Source="{Binding Path=Tag, ElementName=myButton}" />
</devComponent:ButtonDropDown.Image>
</devComponent:ButtonDropDown>
I also tried to use below code but it still does not work because Image does not belong to the visual tree
Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='xxxxx', AncestorLevel='1''
<Button.Image>
<Image Source="{Binding RelativeSource={RelativeSource AncestorType={x:Type mf:MyWindow}}, Path=MyViewModel.ImageData}" />
</Button.Image>
I'm trying to use a view-model-first approach and I've created a view-model for my customized chart control. Now, in my form, I want a TabControl that will display a list of XAML-defined charts defined as such:
<coll:ArrayList x:Key="ChartListTabs" x:Name="ChartList">
<VM:MyChartViewModel x:Name="ChartVM_Today" ChartType="Today" ShortName="Today"/>
<VM:MyChartViewModel x:Name="ChartVM_Week" ChartType="Week" ShortName="This Week"/>
<VM:MyChartViewModel x:Name="ChartVM_Month" ChartType="Month" ShortName="This Month"/>
<VM:MyChartViewModel x:Name="ChartVM_Qtr" ChartType="Quarter" ShortName="This Quarter"/>
<VM:MyChartViewModel x:Name="ChartVM_Year" ChartType="Year" ShortName="This Year"/>
<VM:MyChartViewModel x:Name="ChartVM_Cust" ChartType="Custom" ShortName="Custom"/>
</coll:ArrayList>
Trying to specify data templates for my tab headers and content, I have this:
<DataTemplate x:Key="tab_header">
<TextBlock Text="{Binding ShortName}" FontSize="16" />
</DataTemplate>
<DataTemplate x:Key="tab_content" DataType="{x:Type VM:MyChartViewModel}" >
<local:MyChartControl/>
</DataTemplate>
My TabControl is like this:
<TabControl ItemsSource="{StaticResource ChartListTabs}"
ItemTemplate="{StaticResource tab_header}"
ContentTemplate="{StaticResource tab_content}"
IsSynchronizedWithCurrentItem="True">
<!-- nothing here :) -->
</TabControl>
What happens is that the designer shows the tabs correctly and the first tab content (can't switch tabs because they are dynamically created) showing apparently the right view for the first chart, but when I run the application, all tabs show the same, default, uninitialized content (i.e. the same chart control without any properties set). Also, the instance seems to be the same, i.e. changing something on my custom control (e.g. a date box) this shows on all tabs.
It seems to me that the control (view) in the TabControl content stays the same (TabControl does this, as I've read elsewhere) and should only change DataContext when the tab changes, but it clearly doesn't.
Notes:
All my classes are DependencyObjects and my collections are ObservableCollections (with the exception of the ChartListTabs resource)
ShortName is the view-model property I want to have as tab header text
This question seems related but I can't connect the dots
Here is my solution used your code inside, please try to check this out.
Xaml
<Window x:Class="TabControTemplatingHelpAttempt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"
xmlns:tabControTemplatingHelpAttempt="clr-namespace:TabControTemplatingHelpAttempt"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<collections:ArrayList x:Key="ChartListTabs" x:Name="ChartList">
<tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Today" ChartType="Today" ShortName="Today"/>
<tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Week" ChartType= "Week" ShortName="This Week"/>
<tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Month" ChartType="Month" ShortName="This Month"/>
<tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Qtr" ChartType= "Quarter" ShortName="This Quarter"/>
<tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Year" ChartType= "Year" ShortName="This Year"/>
<tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Cust" ChartType= "Custom" ShortName="Custom"/>
</collections:ArrayList>
<DataTemplate x:Key="TabHeader" DataType="{x:Type tabControTemplatingHelpAttempt:MyChartViewModel}">
<TextBlock Text="{Binding ShortName}" FontSize="16" />
</DataTemplate>
<DataTemplate x:Key="TabContent" DataType="{x:Type tabControTemplatingHelpAttempt:MyChartViewModel}" >
<tabControTemplatingHelpAttempt:MyChartControl Tag="{Binding ChartType}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<TabControl ItemsSource="{StaticResource ChartListTabs}"
ItemTemplate="{StaticResource TabHeader}"
ContentTemplate="{StaticResource TabContent}"
IsSynchronizedWithCurrentItem="True"/>
</Grid></Window>
Converter code
public class ChartType2BrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var key = (ChartType) value;
SolidColorBrush brush;
switch (key)
{
case ChartType.Today:
brush = Brushes.Tomato;
break;
case ChartType.Week:
brush = Brushes.GreenYellow;
break;
case ChartType.Month:
brush = Brushes.Firebrick;
break;
case ChartType.Quarter:
brush = Brushes.Goldenrod;
break;
case ChartType.Year:
brush = Brushes.Teal;
break;
case ChartType.Custom:
brush = Brushes.Blue;
break;
default:
throw new ArgumentOutOfRangeException();
}
return brush;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Main VM
public class MyChartViewModel:BaseObservableDependencyObject
{
private ChartType _chartType;
private string _shortName;
public ChartType ChartType
{
get { return _chartType; }
set
{
_chartType = value;
OnPropertyChanged();
}
}
public string ShortName
{
get { return _shortName; }
set
{
_shortName = value;
OnPropertyChanged();
}
}
}
public enum ChartType
{
Today,
Week,
Month,
Quarter,
Year,
Custom,
}
Inner user control XAML
<UserControl x:Class="TabControTemplatingHelpAttempt.MyChartControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tabControTemplatingHelpAttempt="clr-namespace:TabControTemplatingHelpAttempt">
<UserControl.Resources>
<tabControTemplatingHelpAttempt:ChartType2BrushConverter x:Key="ChartType2BrushConverterKey" />
<DataTemplate x:Key="UserContentTemplateKey" DataType="{x:Type tabControTemplatingHelpAttempt:MyChartViewModel}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
Fill="{Binding ChartType, Converter={StaticResource ChartType2BrushConverterKey}}"/>
<TextBlock Grid.Row="0" Text="{Binding ShortName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Grid Grid.Row="1" Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.ChartType, UpdateSourceTrigger=PropertyChanged}">
<Grid.DataContext>
<tabControTemplatingHelpAttempt:TabContentDataContext/>
</Grid.DataContext>
<Rectangle VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
Fill="{Binding BackgroundBrush}"/>
<TextBlock Text="{Binding Description, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</Grid>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ContentControl Content="{Binding }" ContentTemplate="{StaticResource UserContentTemplateKey}"/>
<!--<Grid.DataContext>
<tabControTemplatingHelpAttempt:TabContentDataContext/>
</Grid.DataContext>
<Rectangle VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
Fill="{Binding BackgroundBrush}"/>
<TextBlock Text="{Binding Code, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" VerticalAlignment="Center" HorizontalAlignment="Center"/>-->
</Grid>
Please keep in attention, that if you comment out the Grid.DataContext tag and comment in the ContentControl tag, your inner content won't be updated since it doesn't created depending on delivered MyChartViewModel. Elsewhere
I can't see any problems with your code.
Inner user control VM
public class TabContentDataContext:BaseObservableObject
{
private string _code;
private Brush _backgroundBrush;
public TabContentDataContext()
{
Init();
}
private void Init()
{
var code = GetCode();
Code = code.ToString();
BackgroundBrush = code%2 == 0 ? Brushes.Red : Brushes.Blue;
}
public virtual int GetCode()
{
return GetHashCode();
}
public string Code
{
get { return _code; }
set
{
_code = value;
OnPropertyChanged();
}
}
public Brush BackgroundBrush
{
get { return _backgroundBrush; }
set
{
_backgroundBrush = value;
OnPropertyChanged();
}
}
}
Observable object code
/// <summary>
/// implements the INotifyPropertyChanged (.net 4.5)
/// </summary>
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
Update
Base Observable Dependency Object code
/// <summary>
/// dependency object that implements the INotifyPropertyChanged (.net 4.5)
/// </summary>
public class BaseObservableDependencyObject : DependencyObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
Regards.
While testing Ilan's answer I found out that when a DataContext is declared inside the control (i.e. as an instance of some class via a UserControl.DataContext tag), it imposes a specific object instance on the control and the ability to databind some other object to it is lost (probably because the WPF run-time uses SetData instead of SetCurrentData).
The recommended way to "test" your control in the designer is the d:DataContext declaration (which works only for the designer).
After spending all day searching for an answer I have to admit defeat and ask for help.
All I need to do is bind the Header item in a context menu to a propety,but all I have tried has failed.
In the code behind I have a simple property that returns a string depending on a flag
Shown Below
namespace myNamespace
{
public partial class MainWindow : System.Windows.Window
{
// Removed Init code for Clarity ......
public OptMenuText optMenuText = new OptMenuText();
public class OptMenuText
{
public bool menuState { get; set; }
public string menuHeader
{
get { if (menuState)
return "String One";
else
return "String Two";
}
}
}
The XAMl code has a ListView bound to a ObservableCollection holding data and various DataTemplates for display
which is working fine. I have read that the Context Menu is not part ot the main application data context and as
such it requires the DataContext to be specified within the Tag (Hope I have this right)
Within the ListView is a Context Menu as shown below
<ListView Name="listView1" Margin="6" SelectionMode="Single"
ItemsSource="{Binding ElementName=This, Path=ConnectCollection}"
ItemTemplateSelector="{StaticResource templateSelector}" IsTextSearchEnabled="False" >
<ListView.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}" >
<MenuItem Header="{Binding menuHeader}" />
<Separator />
<MenuItem Header="Move Item Up" Click="MenuItemUp_Click" />
<MenuItem Header="Move Item Down" Click="MenuItemDown_Click"/>
</ContextMenu>
</ListView.ContextMenu>
</ListView>
My only issue is that I am unable to bind the MenuItem Header to the string property.
Error Reported is
System.Windows.Data Error: 40 : BindingExpression path error: 'menuHeader' property not found on 'object' ''MainWindow' (Name='This')'.
BindingExpression:Path=menuHeader; DataItem='MainWindow' (Name='This'); target element is 'MenuItem' (Name=''); target property is 'Header' (type 'Object')
Can someone PLEASE show me where I am going wrong.
Sarah
I didn't see your nested class in the question. The problem is that you have properties in a nested class and that's why binding fails. You will have to expose optMenuText as a property rather than a public variable. The code below will work.
public partial class MainWindow : System.Windows.Window {
public MainWindow() {
optMenuText = new OptMenuText();
InitializeComponent();
}
public OptMenuText optMenuText { get; set; }
}
and then use
<MenuItem Header="{Binding optMenuText.menuHeader}" />
for your binding
I try to make my own ContentControl that derives from Control to fully understand dark wpf tree concepts. For now, i just implemented the logical part (Content) of the ContentControl.
My code behind :
[ContentProperty("Content")]
public class MyContentControl : Control
{
public MyContentControl()
{
}
public Object Content
{
get { return (Object)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register("Content", typeof(Object), typeof(MyContentControl), new UIPropertyMetadata());
}
XAML :
<StackPanel x:Name="stackPanel">
<TextBlock Visibility="Collapsed" x:Name="textBlock" Text="Hello World"/>
<ContentControl>
<TextBlock Background="LightBlue" Text="{Binding Text, ElementName=textBlock}"/>
</ContentControl>
<local:MyContentControl>
<TextBlock Text="{Binding Text, ElementName=textBlock}"/>
</local:MyContentControl>
</StackPanel>
I got the following binding error :
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=textBlock'. BindingExpression:Path=Text; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
It is like the inner TextBlock can't go up in the logical tree and find the original textblock on which it should bind. I wasn't able to set myContentControl as the parent of the Content object.
Any idee?
Thanks for your time.
Jonas
Relevant question: Binding ElementName. Does it use Visual Tree or Logical Tree
The binding you want is not possible because the same instance of MyContentControl could theoretically be used somewhere else in the application where the element "textBlock" is not in the scope.
If you want to do this type of binding you could use a Resource instead:
xmlns:clr="clr-namespace:System;assembly=mscorlib"
<StackPanel>
<StackPanel.Resources>
<clr:String x:Key="MyText">Hanky Panky</clr:String>
</StackPanel.Resources>
<TextBlock Text="{StaticResource MyText}" />
<ContentControl>
<TextBlock Text="{Binding Source={StaticResource MyText}}" />
</ContentControl>
</StackPanel>
I Just had to apply FrameworkElement.AddLogicalChild and FrameworkElement.RemoveLogicalChild when the ContentChanged and the binding is correctly apply (Verified with WPF Inspector).
So all this is about LogicalTree (and maybe the xaml namescope is inherited from logical parent). The TextBlock inside MyContentControl get the MyContentControl as Parent when MyContentControl.AddLogicalChild(TextBlock) is called.
My Code :
[ContentProperty("Content")]
public class MyContentControl : Control
{
public MyContentControl()
{
Content = new UIElementCollection(this, this);
}
public Object Content
{
get { return (Object)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register("Content", typeof(Object), typeof(MyContentControl), new UIPropertyMetadata(new PropertyChangedCallback(OnContentChanged)));
public static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MyContentControl c = (MyContentControl)d;
if (e.OldValue != null)
{
c.RemoveLogicalChild(e.OldValue);
}
c.AddLogicalChild(e.NewValue);
}
protected override System.Collections.IEnumerator LogicalChildren
{
get
{
List<Object> l = new List<object>();
if (Content != null)
l.Add(Content);
return l.GetEnumerator();
}
}
}
Just hit a very odd issue with databinding which I cannot seem to get to the bottom of:
Scenario
An MVVM View model data bound to a parent form with two properties
public RelayCommand ClearFilteredCategories { get; private set; }
/// <summary>
/// The <see cref="ClearFilterText" /> property's name.
/// </summary>
public const string ClearFilterTextPropertyName = "ClearFilterText";
private string _clearFilterText = "Clear Filter";
/// <summary>
/// Sets and gets the ClearFilterText property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public string ClearFilterText
{
get
{
return _clearFilterText;
}
set
{
if (_clearFilterText == value)
{
return;
}
_clearFilterText = value;
RaisePropertyChanged(ClearFilterTextPropertyName);
}
}
Then I have a User control with Two dependency Properties, thus:
public partial class ClearFilterButton : UserControl
{
public ClearFilterButton()
{
// Required to initialize variables
InitializeComponent();
}
public string ClearFilterString
{
get { return (string)GetValue(ClearFilterStringProperty); }
set { SetValue(ClearFilterStringProperty, value); }
}
public RelayCommand ClearFilterAction
{
get { return (RelayCommand)GetValue(ClearFilterActionProperty); }
set { SetValue(ClearFilterActionProperty, value); }
}
public static readonly DependencyProperty ClearFilterStringProperty =
DependencyProperty.Register("ClearFilterString", typeof(string), typeof(ClearFilterButton), new PropertyMetadata("", ClearFilterString_PropertyChangedCallback));
public static readonly DependencyProperty ClearFilterActionProperty =
DependencyProperty.Register("ClearFilterAction", typeof(RelayCommand), typeof(ClearFilterButton), new PropertyMetadata(null, ClearFilterAction_PropertyChangedCallback));
private static void ClearFilterString_PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//empty
}
private static void ClearFilterAction_PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//empty
}
}
and User Control XAML:
<UserControl
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:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:GalaSoft_MvvmLight_Command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WP71"
mc:Ignorable="d"
x:Class="ATTCookBook.ClearFilterButton"
d:DesignWidth="75" d:DesignHeight="75"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid x:Name="LayoutRoot" Background="Transparent">
<Button HorizontalAlignment="Center" VerticalAlignment="Center" BorderBrush="{x:Null}" Width="75" Height="75">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding ClearFilterAction, Mode=TwoWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Button.Background>
<ImageBrush Stretch="UniformToFill" ImageSource="/icons/appbar.refresh.rest.png"/>
</Button.Background>
</Button>
<TextBlock HorizontalAlignment="Center" Margin="0,0,0,8" TextWrapping="Wrap" Text="{Binding ClearFilterString, Mode=TwoWay}" VerticalAlignment="Bottom" FontSize="13.333" Height="18" Width="0" d:LayoutOverrides="VerticalAlignment"/>
</Grid>
Now when I add this User Control to the Main Page and databind the two View Model Properties through to the User control it gets very weird:
<local:ClearFilterButton Height="Auto" Width="Auto" ClearFilterAction="{Binding ClearFilteredCategories, Mode=TwoWay}" ClearFilterString="{Binding ClearFilterText, Mode=TwoWay}"/>
Because although the Databinding statements above seem fine, the Binding errors with:
System.Windows.Data Error: BindingExpression path error: 'ClearFilteredCategories' property not found on 'ATTCookBook.ClearFilterButton' 'ATTCookBook.ClearFilterButton' (HashCode=126600431). BindingExpression: Path='ClearFilteredCategories' DataItem='ATTCookBook.ClearFilterButton' (HashCode=126600431); target element is 'ATTCookBook.ClearFilterButton' (Name=''); target property is 'ClearFilterAction' (type 'GalaSoft.MvvmLight.Command.RelayCommand')..
System.Windows.Data Error: BindingExpression path error: 'ClearFilterText' property not found on 'ATTCookBook.ClearFilterButton' 'ATTCookBook.ClearFilterButton' (HashCode=126600431). BindingExpression: Path='ClearFilterText' DataItem='ATTCookBook.ClearFilterButton' (HashCode=126600431); target element is 'ATTCookBook.ClearFilterButton' (Name=''); target property is 'ClearFilterString' (type 'System.String')..
Which seems to indicate the View Model is trying to find the parent properties in the child user control?
I do not understand why this could be because I have set a Relative Data Context within the child user control to avoid this and the binding should be passing through the two dependency properties.
I was hoping to make the user control more generic later but cannot seem to even get it working in a basic fashion
Quick call out to you Silverlight Binding masters :D
In your UserControl you are resetting the Datacontext so now the expression
ClearFilterAction="{Binding ClearFilteredCategories, Mode=TwoWay}"
is not relative to the Page where you are using the UserControl but to the UserControl itself. You can encounter the same problem with ItemTemplate datacontext when you want to reference a common command declared on the VM of the Page and not on the object data bound to the template.
You can use a proxy:
ClearFilterAction="{Binding Source={StaticResource
DataContextProxy},Path=DataSource.ClearFilteredCategories}"
The proxy is declared as a Resource:
<Resources:DataContextProxy x:Key="DataContextProxy" />
and the code of the DataProxy class:
public class DataContextProxy : FrameworkElement
{
public DataContextProxy()
{
this.Loaded += new RoutedEventHandler(DataContextProxy_Loaded);
}
void DataContextProxy_Loaded(object sender, RoutedEventArgs e)
{
var binding = new Binding();
if (!String.IsNullOrEmpty(BindingPropertyName))
{
binding.Path = new PropertyPath(BindingPropertyName);
}
binding.Source = this.DataContext;
binding.Mode = BindingMode;
this.SetBinding(DataContextProxy.DataSourceProperty, binding);
}
public Object DataSource
{
get { return (Object)GetValue(DataSourceProperty); }
set { SetValue(DataSourceProperty, value); }
}
public static readonly DependencyProperty DataSourceProperty =
DependencyProperty.Register("DataSource", typeof(Object), typeof(DataContextProxy), null);
public string BindingPropertyName { get; set; }
public BindingMode BindingMode { get; set; }
}
Move the assignment of the data context to usercontrol's root DataGrid.
<UserControl
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:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:GalaSoft_MvvmLight_Command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WP71"
mc:Ignorable="d"
x:Class="ATTCookBook.ClearFilterButton"
d:DesignWidth="75" d:DesignHeight="75">
<Grid x:Name="LayoutRoot" Background="Transparent" DataContext="{Binding Parent, RelativeSource={RelativeSource Self}}" >
<Button HorizontalAlignment="Center" VerticalAlignment="Center" BorderBrush="{x:Null}" Width="75" Height="75">
Resolved in the end with some thanks to the above, however the answer was a lot simpler.
All I had to do was remove any mention of Modes (remove Mode="TwoWay") and remove the DataContext setting from the user control and it just worked.
Just goes to show it's very easy to over-engineer a solution.
I Suspect by adding the Mode setting it was trying to pass the datacontext through the binding and that what it was throwing up (not a very helpful error message)
Hope this helps others. Just keep it simple and work up from there.
For those that are interested, I was basing the implementation from this very useful post - Silverlight UserControl Custom Property Binding