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
Related
I'm trying to learn about DependencyProperty. To do so I want to create a new UserControl which displays a list.
The location of this list must exist in the parent as a property. For this, I only have MainWindow, MainWindowViewModel (these are the parent) and the UserControl (the child) (which is currently using code behind).
In my MainWindow I have
<Grid>
<uc:RecentList MessageList="{Binding Messages}" />
</Grid>
And in the code behind
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
And the ViewModel
public MainWindowViewModel()
{
this.Messages = new ObservableCollection<string>();
this.Messages.Add("Item 1");
this.Messages.Add("Item 2");
this.T = "hi";
}
public ObservableCollection<string> Messages { get; set; }
In the UserControl I have
<Grid>
<ListView ItemsSource="{Binding MessageList}"></ListView>
<TextBlock Text="I'm such text to verify this control is showing" />
</Grid>
And the code behind is
public static readonly DependencyProperty MessageListProperty =
DependencyProperty.Register(
"MessageList", typeof(IEnumerable<string>), typeof(RecentList));
public IEnumerable<string> MessageList
{
get { return (IEnumerable<string>)GetValue(MessageListProperty); }
set { SetValue(MessageListProperty, value); }
}
The issue I have is the binding is not working. I can see this in the Output Window, with the Error:
Error 40 : BindingExpression path error: 'MessageList' property not found on 'object' ''MainWindowViewModel' (HashCode=26034861)'. BindingExpression:Path=MessageList; DataItem='MainWindowViewModel' (HashCode=26034861); target element is 'ListView' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')
I understand the issue but I am confused by it. It's looking in the right place (in the MainWindowViewModel) but it is looking I don't understand why the UserControl is looking for the MessageList in the MainWindowViewModel. I guess it's because that is where I set the datacontext but, I also thought it that if I added this.DataContext = this; to the UserControl's constructor then it's wrong (I've tried it, it didn't work either).
Updating my UserControl to
<ListView ItemsSource="{Binding MessageList, RelativeSource={RelativeSource Mode=TemplatedParent}}"></ListView>
Helps in the sense I don't get the error message, but I also don't see the result.
This is what I think is happening when the application loads:
MainWindow loads
MainWindow then see's the UserControl and notes it requires a property.
Before WPF calls the UserControl constructor, it grabs the value of the property. It then initializes the component and automatically pushes the value to the UserControl's property
How can my UserControl use the Parents (MainWindow) property (Messages)
The Binding in the UserControl's XAML should have the UserControl instance as its source object, e.g. like this:
<ListView ItemsSource="{Binding MessageList,
RelativeSource={RelativeSource AncestorType=UserControl}}" />
Alternatively you could set x:Name on the UserControl and use an ElementName binding:
<UserControl ... x:Name="self">
...
<ListView ItemsSource="{Binding MessageList, ElementName=self}" />
...
</UserControl>
Besides that you should usually not set the DataContext of the UserControl to itself (like DataContext = this;) because that would effectively prevent inheriting the DataContext from the UserControl's parent element, which is necessary for an "external" binding to work, like:
<uc:RecentList MessageList="{Binding Messages}" />
I have the following combobox in the xaml:
<ComboBox x:Name="cmbCharacters1" HorizontalAlignment="Left" Margin="18,21,0,0" VerticalAlignment="Top" Width="136" SelectedIndex="0" Height="32" RenderTransformOrigin="1.53,-1.281"
ItemsSource="{Binding CharacterEntity}" SelectedItem="{Binding Name}" SelectedValue="{Binding Tag}"/>
and the following class and binding code
public class CharacterEntity
{
public string Name { get; set; }
public string Tag { get; set; }
}
....
cmbCharacters1.ItemsSource = characters;//it is a List<CharacterEntity>
when I run it displays the class name instead of the content of Name property, what am I doing wrong?
I think you forgot to use this: DisplayMemberPath="Tag" Or "Name" whatever you wish to display!
You need to set the DisplayMemberPath in your ComboBox XAML.
This isn't a binding, since the ItemsSource is already bound - you just reference the field name, like so:
<ComboBox DisplayMemberPath="Name" ...
In the XAML you are setting the ItemsSource to a class CharacterEntity instead of List<CharacterEntity>, since you are setting the Itemssource in the code-behind remove it from the XAML and try. Also, you need to set DisplayMemberPath="Name" and set either SelectedItem or SelectedValue not both, if you are using SelectedValue then also use SelectedValuePath="Name"
<ComboBox x:Name="cmbCharacters1" SelectedItem="{Binding someCharacter}" DisplayMemberPath="Name" />
See also this answer: https://stackoverflow.com/a/3797074/424129
C#
public class CharacterEntity
{
public string Name { get; set; }
public string Tag { get; set; }
}
// Look up how to implement INotifyPropertyChanged, I didn't bother here
public class MyViewModel : INotifyPropertyChanged {
public MyViewModel(IEnumerable<CharacterEntity> chars)
{
CharacterEntities = new List<CharacterEntity>(chars);
}
private IEnumerable<CharacterEntity> _characterEntities;
public IEnumerable<CharacterEntity> CharacterEntities {
get { return _characterEntities; }
set { _characterEntities = value;
OnPropertyChanged("CharacterEntities"); }
}
private CharacterEntity _characterEntity
public CharacterEntity SelectedCharacterEntity
}
XAML
ItemsSource is the source for the items. Your binding made no sense. You want to give it a list of CharacterEntity, but you bind to the CharacterEntity class? What list are you talking about? And don't set it in code behind. XAML makes much more sense if you use a viewmodel.
Now, somehow the above MyViewModel class needs to be made the DataContext of some control that owns the ComboBox.
<ComboBox HorizontalAlignment="Left" Margin="18,21,0,0"
VerticalAlignment="Top" Width="136" SelectedIndex="0" Height="32"
RenderTransformOrigin="1.53,-1.281"
ItemsSource="{Binding CharacterEntities}"
SelectedItem="{Binding SelectedCharacterEntity}"
DisplayMemberPath="Name"
/>
When you have it like this:
SelectedItem="{Binding Path=Name}"
it will use what ever is now selected in combobox, that class property of Name is being used as Selected. Without Path, you are binding to that combobox Name object. BUT anyways, this shouldn't yet work in your case with Path. So to have it work as you want it to, try this:
Have a SelectedItem binded to CharacterEntity class:
SelectedItem="{Binding SelectedEntity}" // Class instance of CharacterEntity
And then you have a Text binded to that selected entity class property of Name:
Text="{Binding Path=Name}" // Binded to property of Name
SelectedValue="{Binding Path=Tag}" // Binded to property of Tag
This way it should work. You should have a combobox binded to viewmodel and that viewmodel should have a property(class instance of CharacterEntity) of SelectedEntity. Hopefully this helps:
public class CharacterViewModel
{
public CharacterEntity SelectedEntity {get;set;}
public List<CharacterEntity> characters {get;set;} // use ObservableCollection insteand of List(Automatically update UI if list changes)
}
And XAML:
<ComboBox x:Name="cmbCharacters1" HorizontalAlignment="Left" Margin="18,21,0,0" VerticalAlignment="Top" Width="136" SelectedIndex="0" Height="32" RenderTransformOrigin="1.53,-1.281"
ItemsSource="{Binding characters}" Text="{Binding Path=Name}" SelectedItem="{Binding SelectedEntity}" SelectedValue="{Binding Path=Tag}"/>
Also has in codebehind e.g in constructor:
CharacterViewModel charViewModel = new CharacterViewModel();
cmdCharacters1.DataContext = charViewModel;
cmdCharacters1.ItemsSource= charViewModel.characters;
I'm terrible at explaining this, but I hope it makes sense with my code.
Is it the intended behavior that a binding to a collection automatically uses the first item as source?
Example Xaml:
<Window x:Class="ListSelection.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<TextBlock Text="{Binding ColContent}" />
<TextBlock Text="{Binding ItemContent}" />
</StackPanel>
</Window>
and Code:
using System.Collections.Generic;
using System.Windows;
namespace ListSelection
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MyCol("col 1")
{
new MyItem("item 1"),
new MyItem("item 2")
};
}
}
public class MyItem
{
public string ItemContent { get; set; }
public MyItem(string content)
{
ItemContent = content;
}
}
public class MyCol : List<MyItem>
{
public string ColContent { get; set; }
public MyCol(string content)
{
ColContent = content;
}
}
}
The UI shows up with:
col 1
item 1
The second binding took implicitly the first collection item as source! So bug, feature or intended?
EDIT: .net 4.5, VS2012, corrections
EDIT 2:
I further investigated the problem together with a mate and got closer to the solution:
<Window x:Class="ListSelection.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<ListView ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemContent}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBlock Text="{Binding ItemContent}" />
</StackPanel>
</Window>
The - lets call it - magic binding seems to exist for master detail views. By default any collection that is bound gets a CollectionView - which provides a selected item property (and other cool stuff like sorting). This selected item can be used shortcutted for the detailed view. If the IsSynchronizedWithCurrentItem is set to true the shortcutted binding reacts to changed selections. The problem in the whole thing: the selected item of the CollectionView is alway set to the first item which leads to the magic binding... I would call that a bug and it should only work explicitly, e.g. by binding the collection to a Selector with the IsSynchronizedWithCurrentItem set.
Perhaps this is a case of too much cold medicine, but I just can't seem to get this Binding correct.
Here is the (simplified) Window, with the a DataTemplate for each ViewModel type, which should just show an associated View:
<Window ...>
<Window.Resources>
<DataTemplate DataType="{x:Type local:DefaultViewViewModel">
<local:DefaultView />
</DataTemplate>
<DataTemplate DataType="{x:Type other:AnotherViewModel">
<other:AnotherView />
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding CurrentViewModel}" />
</Grid>
</Window>
Here is some of the MainViewModel (the actual ShowABCView methods are Command functions that do more than is shown here, for brevity):
class MainViewModel : ViewModelBase
{
private Stack<ViewModelBase> mContentViewStack;
public MainViewModel()
{
mContentViewStack = new Stack<ViewModelBase>();
ShowDefaultView();
}
public ViewModelBase CurrentViewModel
{
get { return mContentViewStack.Peek(); }
}
private ShowDefaultView()
{
DefaultViewViewModel viewModel = new DefaultViewViewModel();
mContentViewStack.Push(viewModel);
NotifyPropertyChanged("CurrentViewModel");
}
private ShowAnotherView()
{
AnotherViewModel viewModel = new AnotherViewModel();
mContentViewStack.Push(viewModel);
NotifyPropertyChanged("CurrentViewModel");
}
}
And the MainWindow startup code:
public MainWindow()
{
this.DataContext = new MainViewModel();
}
When I run this, I get the error
System.Windows.Data.Error: 40:
BindingExpression path error:
'Content' property not found on
'object' 'DefaultViewViewModel'
I know I'm missing something obvious here, but the Nyquil and friends betray me...
*EDIT - DefaultViewViewModel and DefaultView *
DefaultViewViewModel:
// ViewModelBase is basically just a wrapper for INotifyPropertyChanged,
// plus some other common-to-my-project properties
// (NOT INCLUDING A Content PROPERTY)
class DefaultViewViewModel : ViewModelBase
{
public DefaultViewViewModel() : base()
{
}
}
DefaultView:
<UserControl ...>
<TextBlock Text="Some Hard Coded Text Formatted To My Liking" />
</UserControl>
Well you haven't shown the code for the DefaultViewViewModel yet
but my guess is you defined "Content" as a field and not as a property.
to make sure that it will fix it, go ahead and overkill it by making Content a dependency property
hope that helps
Found the answer upstream from where I was looking. There was an incorrect binding (used regular Binding without RelativeSource of the TemplatedParent) in the base View control that all of our Views use.
No more Nyquil for me...
I've been scratching my head on this one for a while now and am stumped at the moment.
The problem scenario is easier to explain as code so hopefully it speaks for itself. First of all, I have a silverlight application with the following in the XAML...
<UserControl x:Class="SilverlightApplication2.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300">
<UserControl.Resources>
<DataTemplate x:Key="icTemplate">
<ComboBox ItemsSource="{Binding StringsChild}" SelectedItem="{Binding SelectedItem}"/>
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ItemsControl x:Name="ic" ItemTemplate="{StaticResource icTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<Button Click="Save" Grid.Row="1" Content="GO"/>
</Grid>
My code-behind looks like this...(all written in a single class file so that it's easy for you to copy it into your own project and compile)
namespace SilverlightApplication2
{
public partial class Page : UserControl
{
public ObservableCollection<SomeClass> StringsParent { get; set; }
public Page()
{
InitializeComponent();
StringsParent = new ObservableCollection<SomeClass>();
ic.ItemsSource = StringsParent;
}
private void Save(object sender, RoutedEventArgs e)
{
SomeClass c = new SomeClass();
c.StringsChild.Add("First");
c.StringsChild.Add("Second");
c.StringsChild.SetSelectedItem("Second");
StringsParent.Add(c);
}
}
public class SomeClass
{
public SelectableObservablecollection<string> StringsChild { get; set; }
public SomeClass()
{
StringsChild = new SelectableObservablecollection<string>();
}
}
public class SelectableObservablecollection<T> : ObservableCollection<T>
{
public SelectableObservablecollection()
: base()
{
}
public void SetSelectedItem<Q>(Q selectedItem)
{
foreach (T item in this)
{
if (item.Equals(selectedItem))
{
SelectedItem = item;
return;
}
}
}
private T _selectedItem;
public T SelectedItem
{
get
{
return _selectedItem;
}
set
{
_selectedItem = value;
OnPropertyChanged(new PropertyChangedEventArgs("SelectedItem"));
}
}
}
}
So let me explain...
I set out to write a generic way of creating an ObservableCollection that has a SelectedItem property on it so that when I bind the collection to a ComboBox for example, I can Bind the ComboBox's SelectedItem property to it.
However, for some reason, it does not seem to work when the ComboBox is effectively nested via an ItemTemplate. I effectively have a list of lists, a scenario which is simple enough that I'm lost as to what's wrong.
When you run the code you'll see that the templated ComboBox does pick up the correct items, but it's never set to a SelectedItem despite the binding.
I know it's rather long winded, but...any ideas?
Thanks alot
The debugger output actually gives you a hint to the problem:
System.Windows.Data Error: BindingExpression path error: 'SelectedItem' property not found on 'ExpressionElements.SomeClass' 'ExpressionElements.SomeClass' (HashCode=49044892). BindingExpression: Path='SelectedItem' DataItem='ExpressionElements.SomeClass' (HashCode=49044892); target element is 'System.Windows.Controls.ComboBox' (Name=''); target property is 'SelectedItem' (type 'System.Object')..
Because the Data context for the template is an instance of the SomeClass class, all you have to do is change the SelectedItem binding from SelectedItem to StringsChild.SelectedItem:
<DataTemplate x:Key="icTemplate">
<ComboBox ItemsSource="{Binding StringsChild}"
SelectedItem="{Binding StringsChild.SelectedItem}"/>
</DataTemplate>