Controlling the TabControl active tab with a ComboBox - wpf

What i am really trying to achieve is to full control the active TabItem by using the combobox as the navigation control.
Here is what ive got so far:
<TabControl Canvas.Left="26" Canvas.Top="27" Height="100" Name="TabControl1" Width="220">
<TabItem Header="TabItem1" x:Name="TabItem1">
<Grid />
</TabItem>
<TabItem Header="TabItem2" x:Name="TabItem2">
<Grid />
</TabItem>
</TabControl>
<ComboBox Canvas.Left="126" Canvas.Top="134" Height="23" Name="CmbTabs" Width="120"
ItemsSource="{Binding ElementName=TabControl1, Path=Items}"
SelectedValue="{Binding ElementName=TabControl1, Path=SelectedIndex}"
SelectedValuePath="TabIndex"
DisplayMemberPath="Header"/>
Still the only thing that actual works is the list that shows up when i press the togglebutton of the combobox.
Even selecting a tabitem name through the list does not do anything, it does not even update the selected value textbox of the combobox.
Any help ?
Edit:
Ok the answer of Steve Robbins worked fine for the "controling" issue.
What about the fact that selecting an item in the combobox drop down list does not update the value of the combobox? (the comboboxes textbox is still blank!!)

If you're trying to control the TabControl from the Combo then it looks a bit backwards to me.. if you change the SelectedIndex on the tab control to bind it to the combo it should work:
<TabControl Canvas.Left="26" Canvas.Top="27" Height="100" Name="TabControl1" Width="220" SelectedIndex="{Binding ElementName=CmbTabs, Path=SelectedIndex}">
<TabItem Header="TabItem1" x:Name="TabItem1">
<Grid />
</TabItem>
<TabItem Header="TabItem2" x:Name="TabItem2">
<Grid />
</TabItem>
</TabControl>

I started looking at this because of some problems I'm having with the combobox. Unfortunately, I haven't solved my problem but I can provide some additional insight and a workaround to this problem. First off, lets start with my changes to the original xaml.
<TabControl Height="100" Name="TabControl1" Width="220">
<TabItem Header="TabItem1" x:Name="TabItem1">
<TextBlock Text="TabItem1 Content" />
</TabItem>
<TabItem Header="TabItem2" x:Name="TabItem2">
<TextBlock Text="TabItem2 Content" />
</TabItem>
</TabControl>
<ComboBox Height="23" Name="CmbTabs" Width="120"
ItemsSource="{Binding ElementName=TabControl1, Path=Items}"
SelectedIndex="{Binding ElementName=TabControl1, Path=SelectedIndex}"
DisplayMemberPath="Name"
>
</ComboBox>
Notice that instead of creating a binding from the tab control to the ComboBox and vice versa we can create a TwoWay binding (the default in this case) between the SelectedIndex of the tab and combo controls. Next, let's add some content to the TabItems. At this point, similar to Steve's suggestion, we've fixed the "controling" problem. That is, changing the selected TabItem changes the selected ComboBox item (you'll have to trust me on this one or keep reading!) and changing the ComboBox changes the selected TabItem. Great!
The above xaml also changes DiplayMemberPath property to "Name". I think you will find that this eliminates hughdbrown's "weird result". Recall that the Header property (an object) is wrapped by a ContentPresenter. I believe that if no template is supplied the default behavior is to display the Header object as a string in a TextBlock. Thus, the "weird result" correctly reports that the TextBlock control does not contain a Header property.
Now let's make some changes to the previous ComboBox xaml.
<ComboBox Height="23" Name="CmbTabs" Width="120"
ItemsSource="{Binding ElementName=TabControl1, Path=Items}"
SelectedIndex="{Binding ElementName=TabControl1, Path=SelectedIndex}"
>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
This actually produces an interesting result and makes use of the content we added to each TabItem. When this code runs, you'll notice that the ComboBox now displays the selected TabItem's content. Furthermore, the list now displays "System.Windows.Controls.TabItem..." for each TabItem. We can change the TextBlock binding to {Binding Header} and display the Header object but the ComboBox still displays the selected TabItem's content. As it is late on a Friday evening and there just is not enough beer in the world, I didn't look into possible reasons for this. However, I do have a workaround!
First, let's create a ValueConverter to convert the TabControl's Items collection to something we can use. Here's the code.
public class TabItemCollectionConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
ItemCollection collection = value as ItemCollection;
IList<string> names = new List<string>();
foreach (TabItem ti in collection.SourceCollection)
{
names.Add(ti.Header.ToString());
}
return names;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
The converter simply creates a new collection from the TabControl's Items collection that contains the string-ized Header object from each TabItem. This works fine for simple Header objects but obviously has limitations. Now let's consider how we use this in the xaml.
<ComboBox Height="23" Name="CmbTabs" Width="120"
ItemsSource="{Binding ElementName=TabControl1, Path=Items, Converter={StaticResource ItemConverter}}"
SelectedIndex="{Binding ElementName=TabControl1, Path=SelectedIndex}"
>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Remember that a ValueConverter used in an ItemsSource binding returns a new collection. In this case we are converting the TabControl's Items collection to a string collection. Don't forget to create the converter StaticResource! It looks something like this.
<local:TabItemCollectionConverter x:Key="ItemConverter"/>
Now, using the converter, the whole ball of wax works as expected.
What still puzzles me is why the ComboBox displays the TabItem Headers in the list and the TabItem content as the selection. No doubt there is some obvious explanation but as I said, it's Friday...
Hope this helps!

I've been playing with this a lot. There is something about the source of the ComboBox's databinding changing after the selection is made. Or something.
I added the Diagnostics namespace:
xmlns:debug="clr-namespace:System.Diagnostics;assembly=WindowsBase"
And I changed your <Grid /> into TextBoxes with big honking numbers so that I could see that things really were changing:
<TabItem Header="TabItem1" x:Name="TabItem1">
<TextBlock Name="tb1" FontSize="24" Text="1" Width="100" Height="26" />
</TabItem>
And when I ran the app, I found that Diagnostics reports a weird result:
System.Windows.Data Error: 39 :
BindingExpression path error: 'Header'
property not found on 'object'
''TextBlock' (Name='tb1')'.
BindingExpression:Path=Header;
DataItem='TextBlock' (Name='tb2');
target element is 'TextBlock'
(Name=''); target property is 'Text'
(type 'String')
I tried to set the databinding once, in code, at startup:
public Window1()
{
InitializeComponent();
Binding positionBinding = new Binding("Items");
positionBinding.ElementName = "TabControl1";
positionBinding.Path = new PropertyPath("Items");
positionBinding.Mode = BindingMode.OneTime;
CmbTabs.SetBinding(ComboBox.ItemsSourceProperty, positionBinding);
CmbTabs.DisplayMemberPath = "Header";
}
And it still switches the CombobBox to show no selected item after the selection and change of TabItem is made. It's as if the DataContext is switched to the TabControl.TabItem.TextBlock after the TabControl changes selection.
So I don't exactly have an answer for you but I have some results for you to work on.
Bea Stollnitz has a good article on using this diagnostic technique. "How can I debug WPF bindings?"

Building off of hughdbrown's answer, I found this post on MSDN that describes your problem as a bug. You can reproduce it with this XAML (which has the opposite problem as your XAML):
<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TabControl Name="TabControl1" Width="220" Height="100" Canvas.Left="26" Canvas.Top="27">
<TabItem x:Name="TabItem1" Header="TabItem1">
foobar
</TabItem>
<TabItem x:Name="TabItem2" Header="TabItem2">
fizzbuzz
</TabItem>
</TabControl>
<ComboBox Name="CmbTabs" Width="120" Height="23" Canvas.Left="126" Canvas.Top="134"
ItemsSource="{Binding ElementName=TabControl1, Path=Items}"
DisplayMemberPath="Length"
SelectedIndex="{Binding ElementName=TabControl1, Path=SelectedIndex}"/>
</Canvas>
As you can see the Length binding works fine except for in the dropdown, where it is going off the TabItem instead of the string inside.
I'm not sure it's ideal for your purposes, but you can get around it by being a little less elegant and reproducing your headers in ComboBoxItems:
<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TabControl Name="TabControl1" Width="220" Height="100" Canvas.Left="26" Canvas.Top="27">
<TabItem x:Name="TabItem1" Header="TabItem1">
<Grid/>
</TabItem>
<TabItem x:Name="TabItem2" Header="TabItem2">
<Grid/>
</TabItem>
</TabControl>
<ComboBox Name="CmbTabs" Width="120" Height="23" Canvas.Left="126" Canvas.Top="134"
SelectedIndex="{Binding ElementName=TabControl1, Path=SelectedIndex}">
<ComboBoxItem>TabItem1</ComboBoxItem>
<ComboBoxItem>TabItem2</ComboBoxItem>
</ComboBox>
</Canvas>

Related

Can't show the DataTemplate on a selected item in combobox?

Newbie question. In the following combobox, the drop-down list correctly displays the company name and phone number as per the DataTemplate. However, selecting an item from the drop-down (by mouse) resutls only in showing the object name:
Stargate_V.DataService.View_Small_Company
The selected company name and phone number is not shown. What am I doing wrong?
<ComboBox
ItemsSource="{Binding PrimaryInsurance.Companies}"
ItemTemplate="{StaticResource CompanyTemplate}"
IsEditable="True"
IsTextSearchEnabled="True"
Height="20" HorizontalAlignment="Left" Margin="375,235,0,0" VerticalAlignment="Top" Width="198" />
The CompanyTemplate is:
<DataTemplate x:Key="CompanyTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Companyname}" Width="240"/>
<TextBlock Text="|" Width="10"/>
<TextBlock Text="{Binding Phone}" Width="80" />
</StackPanel>
</DataTemplate>
TIA
The issue that you are having is the combination of displaying a complex type (i.e. Class) with a DataTemplate and your ComboBox has IsEditable set to true. When setting IsEditable to true the ComboBox doesn't know which property to use for searching so it just calls ToString on the object and displays the results. To fix this issue you need to set the TextSearch.TextPath to the property name on the object that you would like to use for searching. Once you do that it will display that property in the selection box instead of the result of ToString. Below is what your XAML should look like if you wanted to search on the Companyname property on your object.
<ComboBox
ItemsSource="{Binding PrimaryInsurance.Companies}"
ItemTemplate="{StaticResource CompanyTemplate}"
IsEditable="True"
IsTextSearchEnabled="True"
TextSearch.TextPath="Companyname"
Height="20" HorizontalAlignment="Left" Margin="375,235,0,0" VerticalAlignment="Top" Width="198" />

WPF - Filtering a DataCollection with an autocompletebox

I have a view and ViewModel that are working perfectly. I have recently added an AutocompleteBox (found in the WPF Toolkit) which allows users to quickly look up an item.
My view is as such:
An ItemsControl containing my CollectionViewSource named People. Generating perfectly
An AutocompleteBox where the dropdown shows only the items containing the values the user is typing in the AutocompleteBox. Works well. If I type John, all of the people in my CollectionViewSource named People with the word John in the name appear in the dropdown.
My issue is: how do I filter my ItemsControl when the user selects the item he wishes to see from the Dropdown?
My code so far in XAML to bind the data:
<toolkit:AutoCompleteBox Height="25" Width="400"
Foreground="AliceBlue"
ItemsSource="{Binding People.View}"
ValueMemberPath="Name"
Text="{Binding Name}"
IsTextCompletionEnabled="True"
FilterMode="Contains"
Background="#303030">
<toolkit:AutoCompleteBox.ItemTemplate>
<DataTemplate>
<Grid Width="360" HorizontalAlignment="Left">
<StackPanel Orientation="Vertical" HorizontalAlignment="Left"
VerticalAlignment="Top" Width="300">
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" Foreground="#25A0DA"
FontSize="14" Width="300"/>
<TextBlock Text="{Binding Status}" FontWeight="Normal" Foreground="White"
FontSize="10" Width="300"/>
</StackPanel>
</Grid>
</DataTemplate>
</toolkit:AutoCompleteBox.ItemTemplate>
</toolkit:AutoCompleteBox>
<ItemsControl x:Name="tStack" Grid.Column="0" Grid.Row="1"
ItemsSource="{Binding People.View}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
BorderThickness="0.5">
</ItemsControl>
The itemsControl is styled and the format of the items inside it are also templated/styled but far too long and unconstructive to post here.
From the Name property setter in your viewmodel to which toolkit:AutoCompleteBox.Text is bound, you will have to filter the ObservableCollection backing your Collectionview i,e ItemsSource of your ItemsControl.
If you have your Collectionsource with you then you can have filter applied to it like below:
ICollectionView _peopleView = CollectionViewSource.GetDefaultView(peoples);
_peopleView .Filter = PeopleFilter
private bool PeopleFilter(object item)
{
People people= item as People;
return people.Name.Contains( _filterString ); //Here filter string will be your Name prperty value
}
From Setter of name property you will hav to call _peopleView .Refresh(); to apply the filter
Thanks

Binding the content of a ListBoxItem to something not related to

I'm new to WPF, but am pretty familiar with binding list box controls to observable collections in the view model.
In my current project we have a ListBox that is used for navigating to different pages in a frame box. I want to add some display information to the first ListBoxItem to show which object (in this case, the Scenario) is being worked on (it is selected in a previous frame that visible in the subsequent frames). The ListBox itself is using a static list defined in the xaml, so it isn't bound to anything in the ViewModel. The CurrentScenario is a property on the ViewModel. I was able to add a Label to the same window that contains this ListBox and successfully bind CurrentScenario.Id to its content, and it updated correctly, so I know that the path in the Binding statement should resolve correctly.
<ListBox
Style="{StaticResource FunctionBackground}"
IsSynchronizedWithCurrentItem="True"
>
<ListBoxItem Style="{StaticResource FunctionListBoxItemStyle}">
<ListBoxItem.ContentTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Scenario" />
<TextBlock Grid.Row="1" Text="{Binding Path=CurrentScenario.Id}"/>
</Grid>
</DataTemplate>
</ListBoxItem.ContentTemplate>
</ListBoxItem>
<ListBoxItem Style="{StaticResource FunctionListBoxItemStyle}" >Parameter</ListBoxItem>
<ListBoxItem Style="{StaticResource FunctionListBoxItemStyle}" >Run</ListBoxItem>
<ListBoxItem Style="{StaticResource FunctionListBoxItemStyle}" >Results</ListBoxItem>
</ListBox>
When I try to add this extra information to the listbox item, from what I can tell, the list box item has an empty text block below the text block with the word "Scenario." I can't figure out why the empty text box content is not showing the value of the bound property. When I put a normal string in the Text property of the second text block, it shows up correctly.
I imagine that either ListBoxItem content is only set up be bound to properties related to the ItemSource, and so it ignores attempts to bind to other things, or maybe there is something fundamental in WPF that I am missing. Or both...
Thanks if anyone has any ideas!
So if property CurrentScenario is in ViewModel you can use RelativeSource to binding to this property.
...
<TextBlock Grid.Row="0" Text="Scenario" />
<TextBlock Grid.Row="1" Text="{Binding Path=DataContext.CurrentScenario.Id, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
...

Pattern for working with a form in Silverlight 4? (How to get references to XAML elements)

I have a form:
<StackPanel Orientation="Horizontal" Visibility="{Binding Editable, Converter={StaticResource visibilityConverter}}"
ToolTipService.ToolTip="Add new topic to this group">
<sdk:AutoCompleteBox Width="160" ItemsSource="{Binding ElementName=LayoutRoot, Path=DataContext.TopicNames}" />
<Button Click="addTopicButton_Click">
<Image Source="Images/appbar.add.rest.png" />
</Button>
</StackPanel>
This form appears in a DataTemplate for an ItemsControl. I'm not sure what the best way is to get the data from the AutoCompleteBox when the button is clicked. I can't give the elements x:Name attributes, because they're in a template (right?).
How can I get around this? The Click event will give me the Button, but I need a reference to the text box. Use the Button's parent, then look through the children for the Textbox? If I factored this out into its own UserControl, I could set x:Name values, but I'd rather not do that.
Any other ideas?
Update: Here is another example of such a problem:
<ListBox x:Name="topicList"
ItemsSource="{Binding Id, Converter={StaticResource topicGroupIDConverter}}"
SelectionChanged="ListBox_SelectionChanged"
HorizontalAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"
Width="150"
VerticalAlignment="Center"
ToolTipService.ToolTip="{Binding Description}"
ToolTipService.Placement="Right" />
<Button ToolTipService.ToolTip="Remove this topic from this group"
Visibility="{Binding ElementName=topicList,
Path=DataContext.Editable,
Converter={StaticResource visibilityConverter}}"
Click="removeTopicButton_Click"
HorizontalAlignment="Right"
Margin="10,0">
<Image Source="Images/appbar.cancel.rest.png" />
</Button>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
When the button is clicked, I want to access topicList.DataContext. However, topicList itself is a DataTemplate in an ItemsControl, so I can't access it using its name from code-behind. How else can I do this?
You can add a property, say SelectedItemInAutoCompleteBox, to your presenter, and then can bind it to the SelectedItem property of AutoCompleteBox, using Mode=TwoWay, like this,
<sdk:AutoCompleteBox SelectedItem="{Binding Path=DataContext.SelectedItemInAutoCompleteBox, Mode=TwoWay}" ... />
You may try the same approach with Text property of AutoCompleteBox, also. See if it solves your problem.:-)
You have several choices:
If you're on Silverlight 5, use the AncestorBinding
Otherwise, use a Silverlight 4 AncestorBinding hack (it doesn't look pretty)
Or you could try DataContextProxy, which stores the DataContext in a resource so that it is accessible. Note: you should set the DataContextProxy as a Resource of topicList ListBox, not the UserControl as in Dan Wahlin's example.

WPF UserControl Binding Problem

I have the following user control that is embedded within another user control.
<UserControl.Resources>
<DataTemplate x:Key="ContextsTemplate">
<Label Margin="10,0,20,0" Content="{Binding Name}"/>
</DataTemplate>
</UserControl.Resources>
<ItemsControl Name="Contexts"
Background="Transparent"
ItemsSource="{Binding}"
Margin="0,0,0,0"
VerticalAlignment="Center"
AlternationCount="2"
ItemTemplate="{StaticResource ContextsTemplate}">
</ItemsControl>
Here is XAML code for the user control (controlB) above that is embedded in another user control (controlA).
<local:ContextContentsUserControl Height="30" Content="{Binding Contexts}"/>
controlA is displayed on the screen as "(Collection)" but for some reason doesn't show each item in the collections text in the label. Please help.
The problem is here:
Content="{Binding Contexts}"
You meant:
DataContext="{Binding Contexts}"
The reason you got "(Collection)" instead of the content you had defined for controlA is the content you defined in the XAML for ControlA was replaced with your collection. The body of a UserControl's XAML file simply sets its Content property: You replaced it after it was set.
When you declare your ContextContentsUserControl, you are setting its Content property. You need to be setting the DataContext instead:
<local:ContextContentsUserControl Height="30" DataContext="{Binding Contexts}" />

Resources