I have a list of tab items that have views dynamically added to them. Every time a user adds a view, a new tab item is created. I'm now trying to bind a menu to a tabcontrol's items so that a user can select from a menu which view is currently the active view.
My menu is bound as such:
<Menu Background="Transparent">
<MenuItem Style="{StaticResource TabMenuButtonStyle}" ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}, Path=Items}" ItemContainerStyle="{StaticResource TabMenuItem}"></MenuItem>
</Menu>
This works fine and has the desired effect (each menu item is a listing of all the open tabs).
I have the following style that binds menu items to the IsSelected property of the tab items:
<Setter Property="IsChecked" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
My problem is, this binding doesn't work. The binding error message is stating that it can't find the IsSelected property on the view object. I don't want it to use the specfic view, rather, I want it to look at the tab item that the view is currently bound to.
I've tried the following, but still get a binding error:
<Setter Property="IsChecked" Value="{Binding Path=IsSelected, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=TabItem}}}" />
Which states that it can't find an ancestor of type TabItem for each menu item (which makes sense as the menu item's ancestors are not what it is bound to.)
Is there any way I can get access to the parent of the item that is coming in as a binding so I can bind to its properties?
Update:
Per Yadyn's advice, I decided to create a value converter and return tab items.
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
ItemCollection ic = (ItemCollection)value;
List<TabItem> tabItems = new List<TabItem>();
foreach (var obj in ic) {
tabItems.Add((TabItem)obj);
}
return tabItems;
}
This makes binding IsSelected to IsChecked work for static items (TabControls that have their tab items already created), but for the dynamically added views, the Convert method never gets called. It's like the TabControl is not sending out an update to binders of its items that something has changed. Here is how the MenuItem is wired up now:
<MenuItem Style="{StaticResource TabMenuButtonStyle}" ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}, Path=Items, Mode=OneWay, NotifyOnSourceUpdated=True, Converter={StaticResource TabControlItemConverter}}" ItemContainerStyle="{StaticResource TabMenuItem}"></MenuItem>
TabControl.Items will get you back the views, since that is what you've bound to your TabControl to have dynamic tab views.
Unfortunately, there isn't a property you can bind to on the TabControl directly that will get you a collection of the TabItems. These are actually the ItemContainers for each item in the Items bound collection.
What you might do is create a converter or something. You can try using myTabControl.ItemContainerGenerator.ContainerFromItem and pass in the view object to get back the actual TabItem that wraps it. Then your IsSelected binding will work.
You might consider binding directly to the TabControl itself instead of the Items property. Then the converter can easily do the above call to ContainerFromItem. You'll then have to return a List<TabItems> from the converter by enumerating the Items property yourself (calling ContainerFromItem for each).
Anyway, hopefully this gets you on the right track!
Here is something simpler. Define a toplevel viewmodel that holds a collection of viewmodels representing the tabs and menuitems like so
//Not showing here the details of implementing INPC
public class MyCustomCompositeViewModel:INotifyPropertyChanged
{
public ObservableCollection<CompositeViewItem>CompositeItems{get;set;}
public CompositeViewItem SelectedItem{get;set;}
}
On the view, bind the tabitems to the CompositeItems collection and bind the selected tab item to the selectedItem. You can bind the MenuItems similarly
The compositeviewitem should provide properties like the name of the item (for display on the tab and menu), and perhaps the additional data the View needs for rendering. Hope this makes sense.
Related
I am trying to get the bool value of the checkbox present in the Listview. I am binding to a bool public property "Assignm" in the view model. I tried the below binding pattern but the problem is if i select one checkbox it selects all checkboxes and vice versa. I think this is because relativesource is listview and it works on complete listview. I also tried changing the relative source to ListviewItem but that didn't trigger anything. Can someone help me please. Do i need to change something here ?
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox Tag="{Binding MU_Identifier}" IsChecked="{Binding DataContext.Assignm, RelativeSource={RelativeSource FindAncestor, AncestorType=ListView}}">
</CheckBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
Because your binding for IsChecked property is Assignm property which seems to be one property of your view model.
If there is a boolean property named Assignm for the data model of the DataSource of ListView, then just change the binding like this: {Binding Assignm}, as Tag property does.
All your items are bounded to a single property, so when one item changes a property in your context it changes on other items.
To provide correct work all your items from ItemsSource should have property IsChecked.
Check this Example
Purely for code and template simplification I want to be able to have each item inside my items control bound to a property inside each array element. For example
public List<MyObject> MyObjectList; //If I bind to this, each item recieves MyObject as its data context.
I want the ability for each item to recieve MyObject.SomeProperty as its data context. I thought through setting ItemContainerStyle I could set the datacontext but that doesn't appear to work. Does anybody have any ideas?
Somthing along the lines of the following, so each Item is bound to the Property "FieldValue" inside of each object. But in this case, it grabs the "FieldValue" binding from the root object, not each individual item.
<ItemsControl ItemsSource="{Binding MyArrayOfObjects, ItemTemplateSelector="{StaticResource MyObjectTemplateSelector}">
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Control.DataContext" Value="{Binding FieldValue}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
Thanks in advance.
I have a TabControl which binds an ObservableCollecion<T>. Every tab represents one object from OC. In the tab header i have a button:
<Button Command="{Binding DeleteCommand}">x</Button>
which should delete an object from the observable collection and consequently represent the result as the deleted tab. However the command is not acknowledged when i click on the x button, probably because it searches for a property in its model(?). Is there any way this would work?
You can bind to TabControl's DataContext using RelativeSource markup extension.
Also, in case you want to remove the item from ObservableCollection, pass the binding via CommandParameter which will be an instance of T (model object).
<Button Command="{Binding DataContext.TestCommand,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=TabControl}}"
CommandParameter="{Binding}"/>
I have a list of objects which i want to bind to a ListView control in my WPF application.
The Objects have a DataTemplate already, so no need to define that.
The list of objects is a property in the codebehind file in the format list<object>
When i add one object programatically, it appears fine. But when i try to bind the ItemSource of the ListBox to the list of objects, nothing shows up.
I am using the following binding:
<ListBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=Portfolios}"/>
where the name of the property i am trying to bind to is Portfolios and exists on the parent window
List<> objects don't automatically report when a new item is added. Try using an ObservableCollection<> instead, and see if that helps.
I want to build a simple application with the MVVM pattern.
This application will have two main parts:
menu on top
content below
The navigation will be simple:
each menu item (e.g. "Manage Customers" or "View Reports") will fill the content area with a new page that has some particular functionality
I have done this before with code behind where the code-behind event-handler for menu items had all pages loaded and the one that should be displayed was loaded in as a child of a StackPanel. This, however, will not work in MVVM since you don't want to be manually filling a StackPanel but displaying e.g. a "PageItem" object with a DataTemplate, etc.
So those of you who have made a simple click-menu application like this with MVVM, what was your basic application structure? I'm thinking along these lines:
MainView.xaml:
<DockPanel LastChildFill="False">
<Menu
ItemsSource="{Binding PageItemsMainMenu}"
ItemTemplate="{StaticResource MainMenuStyle}"/>
<ContentControl
Content="{Binding SelectedPageItem}"/>
</DockPanel>
where the Menu is filled with a collection of "PageItems" and the DataTemplate displays the Title of each "PageItem object" as the Header of each MenuItem.
And the ContentControl will be filled with a View/ViewModel pair which has full functionality, but am not sure on this.
First, I think you should keep the code-behind event handler, there's no point in changing a simple 2 line event handler to a complex command driven monster for no practical reason (and don't say testebility, this is the main menu, it will be tested every time you run the app).
Now, if you do want to go the pure MVVM route, all you have to do it to make your menu fire a command, first, in some resource section add this style:
<Style x:Key="MenuItemStyle" TargetType="MenuItem">
<Setter Property="Command"
Value="{Binding DataContext.SwitchViewCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Menu}}}"/>
<Setter Property="CommandParameter"
Value="{Binding}"/>
</Style>
This style will make the menu item fire a the SwitchViewCommand on the attached view model with the MenuItem's DataContext as the command parameter.
The actual view is the same as your code with an additional reference to that style as the ItemContainerStyle (so it applies to the menu item and not the content of the DataTemplate):
<DockPanel LastChildFill="False">
<Menu DockPanel.Dock="Top"
ItemsSource="{Binding PageItemsMainMenu}"
ItemTemplate="{StaticResource MainMenuStyle}"
ItemContainerStyle="{StaticResource MenuItemStyle}"/>
<ContentControl
Content="{Binding SelectedPageItem}"/>
</DockPanel>
Now in the view model you need (I used strings because I don't have your PageItem code):
private string _selectedViewItem;
public List<string> PageItemsMainMenu { get; set; }
public string SelectedPageItem
{
get { return _selectedViewItem; }
set { _selectedViewItem = value; OnNotifyPropertyChanged("SelectedPageItem"); }
}
public ICommand SwitchViewCommand { get; set; }
And use whatever command class you use to make the command call this code:
private void DoSwitchViewCommand(object parameter)
{
SelectedPageItem = (string)parameter;
}
Now, when the user clicks a menu item the menu item will call the SwitchViewCommand with the page item as the parameter.
The command will call the DoSwitchViewCommand that will set the SelectedPageItem property
The property will raise the NotifyPropertyChanged that will make the UI update via data binding.
Or, you can write a 2 line event handler, your choice
i could imagine an ObservableCollection in the VM, that holds all the pages to be callable from the menu.
Then bind an ItemsControl And the ContentControl to it to make the ContentControl always show the CurrentItem from that List.
Of course, the menu will only bind to some Title property
whereas the ContentControl will adopt the whole item and plug in some appropriate view according to the type.
Another option is to use a ListBox instead of a menu, style the ListBox to look like a menu and then you can bind to the selected value, like this:
<DockPanel LastChildFill="False">
<ListBox
ItemsSource="{Binding PageItemsMainMenu}"
ItemTemplate="{StaticResource MainMenuStyle}"
IsSynchronizedWithCurrentItem="True"/>
<ContentControl
Content="{Binding PageItemsMainMenu/}"/>
</DockPanel>
Note the IsSynchronizedWithCurrentItem="True" to set the selected item and the {Binding PageItemsMainMenu/} with the trailing slash to use it.