I currently have a tab control containing multiple tab items, where each tab item contains different custom user control. I would like the tab headers to turn red when the associated tab contains a validation failure. My validations are implemented as ValidationRules on the appropriate bindings (moving to IDataError or another validation approach is not a feasible solution). Each tab specific control tracks it's errors through the bubbling ValidationErrorsEvent and exposes a count.
I am currently using x:Name on the tab specific controls & ElementName in the TabItem headers to bind the count exposed by the tab specific controls to the color of the text in the header (via a converter).
<TabControl>
<TabItem>
<TabItem.Header>
<TextBlock Text="Tab 1" Foreground="{Binding Errors.Count, ElementName=_tabOne, Converter={StaticResource ErrorCountToColorConverter}}" />
</TabItem.Header>
<AdornerDecorator>
<my:CustomTabOneControl x:Name="_tabOne" />
</AdornerDecorator>
</TabItem>
<TabItem>
<TabItem.Header>
<TextBlock Text="Tab 2" Foreground="{Binding Errors.Count, ElementName=_tabTwo, Converter={StaticResource ErrorCountToColorConverter}}" />
</TabItem.Header>
<AdornerDecorator>
<my:CustomTabTwoControl x:Name="_tabTwo" />
</AdornerDecorator>
</TabItem>
</TabControl>
Due to the lazyness of WPFs tab control, the validation of each tab does not occur until it is opened. As such the headers for tabs containing invalid fields do not turn red until the tab has been opened (after that they remain correct).
Can anyone suggest a way of resolving this issue, or an alternative approach to achieve the same tab highlighting?
Have a look at this post and answer; it is a lot of work and possibly maintenance but it does work by using a multitrigger that sets the header template based on the HasError property of the controls. Unfortunately it requires you to add a condition to the trigger for each control that should influence the state of the header.
You could try combine this with the answer to this post: Detecting WPF Validation Errors
that walks the visual tree to find Validation Errors. Thereby making it dynamic and less dependent on maintaining the trigger conditions when building the UI.
Related
I have custom control like below. When press tab key focus will move in the order elements arrangement.
Query:
When stackpanel receive tab focus I need to change default tab order toggle button present in stackpanel
Default Tab Order:
DockPanel--Border---StackPanel-->Button1-->button2-->button3
Expected Order
DockPanel--Border---StackPanel-->Button3-->button2-->button1
I need update TabOrder based on its parent. Please suggestion solution modify the tab order based on parent
Note: I need UI as like below arrangements, only i need to modify the tab order for buttons
<DockPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<Border x:Name="MainBorder">
<StackPanel>
<ToggleButton>Button 1</ToggleButton>
<ToggleButton>Button 3</ToggleButton>
<ToggleButton>Button 3</ToggleButton>
</StackPanel>
</Border>
</DockPanel>
As mentioned in comments do set the TabIndex property. To step within control do use KeyboardNavigation.TabNavigation attached property.
<DockPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<Border x:Name="MainBorder">
<StackPanel KeyboardNavigation.TabNavigation="Local">
<ToggleButton KeyboardNavigation.TabIndex="3">Button 1</ToggleButton>
<ToggleButton KeyboardNavigation.TabIndex="2">Button 2</ToggleButton>
<ToggleButton KeyboardNavigation.TabIndex="1">Button 3</ToggleButton>
</StackPanel>
</Border>
</DockPanel>
If you want to modify the tab order at run time I would advice you to create a behavior for it. See Use of Behavior in WPF MVVM? To access attached property from code see Get and set WPF custom attached property from code behind
I'm using dragablz:TabablzControl in a project of mine and I have the need of hide/show some tabs dinamically.
The fact is the control is not respecting the property Visibility of the TabItem.
I've tried with and without binding, like this:
<dragablz:TabablzControl Grid.Row="2" BorderThickness="0" FixedHeaderCount="20">
<TabItem Header="Additional Info" Visibility="{Binding ShowAdditionalInfoTab, Converter={StaticResource BooleanToVisibilityConverter}}">
<controls:AdditionalInfoControl />
</TabItem>
<TabItem Header="Additional Info" Visibility="Collapsed">
<controls:AdditionalInfoControl />
</TabItem>
</dragablz:TabablzControl>
But none is working. Change the "FixedHeaderCount" does not affect the result.
The tab remains always visible.
Is there any other way that I can achieve the result I need?
I've received a response from the development team, and I'm leaving it here for anyone who has the same problem.
Yeah, obviously there’s since changes to the standard tab control to support all of the extra features, and currently that’s not supported. You’d have to temporarily remove your hidden item from the source.
I'm trying to find the best solution for a TabControl that both support a close button on each TabItem, and always show a "new tab button" as the last tab.
I've found some half working solutions, but i think that was for MVVM, that I'm not using. Enough to try to understand WPF =)
This is the best solution I've found so far:
http://www.codeproject.com/Articles/493538/Add-Remove-Tabs-Dynamically-in-WPF
A solution that i actually understand. But the problem is that it is using the ItemsSource, and i don't want that. I want to bind the ItemsSource to my own collection without having to have special things in that collection to handle the new tab button.
I've been search for days now but cant find a good solution.
And I'm really new to WPF, otherwise i could probably have adapted the half done solutions I've found, or make them complete. But unfortunately that is way out of my league for now.
Any help appreciated.
I have an open source library which supports MVVM and allows extra content, such as a button to be added into the tab strip. It is sports Chrome style tabs which can tear off.
http://dragablz.net
This is bit of a dirty way to achieve the Add (+) button placed next to the last TabItem without much work. You already know how to place a Delete button next to the TabItem caption so I've not included that logic here.
Basically the logic in this solution is
To bind ItemsSource property to your own collection as well as
the Add TabItem using a CompositeCollection.
Disable selection of
the Add(+) TabItem and instead perform an action to load a new tab when it
is clicked/selected.
XAML bit
<TextBlock x:Name="HiddenItemWithDataContext" Visibility="Collapsed" />
<TabControl x:Name="Tab1" SelectionChanged="Tab1_SelectionChanged" >
<TabControl.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding DataContext.MyList, Source={x:Reference HiddenItemWithDataContext}}" />
<TabItem Height="0" Width="0" />
<TabItem Header="+" x:Name="AddTabButton"/>
</CompositeCollection>
</TabControl.ItemsSource>
</TabControl>
The code behind
private void Tab1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Contains(AddTabButton))
{
//Logic for adding a new item to the bound collection goes here.
string newItem = "Item " + (MyList.Count + 1);
MyList.Add(newItem);
e.Handled = true;
Dispatcher.BeginInvoke(new Action(() => Tab1.SelectedItem = newItem));
}
}
You could make a converter which appends the Add tab. This way the collection of tabs in you viewmodel will only contain the real tabs.
The problem is then how to know when the Add tab is selected. You could make a TabItem behavior which executes a command when the tab is selected. Incidentally I recommended this for another question just recently, so you can take the code from there: TabItem selected behavior
While I don't actually have the coded solution, I can give some insight on what is most likely the appropriate way to handle this in a WPF/MVVM pattern.
Firstly, if we break down the request it is as follows:
You have a sequence of elements that you want to display.
You want the user to be able to remove an individual element from the sequence.
You want the user to be able to add a new element to the sequence.
Additionally, since you are attempting to use a TabControl, you are also looking to get the behavior that a Selector control provides (element selection), as well as an area to display the element (content) which is selected.
So, if we stick to these behaviors you'll be fine, since the user interface controls can be customized in terms of look and feel.
Of course, the best control for this is the TabControl, which are you already trying to use. If we use this control, it satisfies the first item.
<TabControl ItemsSource="{Binding Path=Customers}" />
Afterwards, you can customize each element, in your case you want to add a Button to each element which will execute a command to remove that element from the sequence. This will satisfy the second item.
<TabControl ...>
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=CustomerId}" />
<Button Command="{Binding Path=RemoveItemCommand, Mode=OneTime,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type TabControl}}"
CommandParameter="{Binding}" />
</StackPanel>
</DataTemplate>
<TabControl.ItemTemplate>
</TabControl>
The last part is a bit more difficult, and will require you to actually have to create a custom control that inherits from the TabControl class, add an ICommand DependencyProperty, and customize the control template so that it not only displays the TabPanel, but right next to it also displays a Button which handles the DependencyProperty you just created (the look and feel of the button will have to be customized as well). Doing all of this will allow you to display your own version of a TabControl which has a faux TabItem, which of course is your "Add" button. This is far far far easier said than done, and I wish you luck. Just remember that the TabPanel wraps onto multiple rows and can go both horizontally or vertically. Basically, this last part is not easy at all.
I heed to create wizard and in the wizard I have tab control which have to call to the user control according to the context,I need to create the wizard which will able to invoke
different pages according to the user selection ,currently I call to the pages as follows which I think is not the right way,any Idea how should I do it via code (not in the xaml )i.e. according to some decision invoke the suitable page to the tab control.
this is the xaml:
<Border Grid.Column="1" Name="MainBorder">
<TabControl x:Name="MainTabControl" Height="638" VerticalAlignment="Bottom">
<TabItem Visibility="Collapsed" >
<Frame Source="page1.xaml" />
</TabItem>
<TabItem Visibility="Collapsed" >
<Frame Source="page2.xaml"/>
</TabItem>
<TabItem Visibility="Collapsed" Header="Step 3">
<TextBlock Text="Page 3"/>
</TabItem>
<TabItem Visibility="Collapsed" Header="Step 4">
<TextBlock Text="Page 4"/>
</TabItem>
</TabControl>
</Border>
UPDATE
I was tried in the main window like the following without success
create new tab by code and add to it the page 1 and then add it to the MainTabControl
TabControl tabControl = new TabControl(new Page1());
MainTabControl.add..
.
there is no add in the main tab control
For this scenario, I would use a Frame rather that tabs. The frame allows you to manage the flow of it's content via the NavigationService. You can use Uri's to display a page via the Frame.Source property, or a FrameworkElement via the Frame.Content property. Both are DependencyProperties and can therefore be bound to.
Paul Stovel wrote an excellent blog on this called WPF Navigation. Everything you need to create a wizard from a frame can be found in this blog, including passing values between pages and templating of the Frame to simply handle the display of navigation buttons.
I would agree with Mark, it is a lot easier to use NavigationWindows than TabControls.
I've worked on a lot of interfaces like this and written up some of the basic things with,
WPF Wizards, Part 1
WPF Wizards, Part 2
Then more recently I worked out how to get the styling just right
Styling Wizards
In fact I've released the styling and examples as open source at
WinChrome
There is some simple example code including use of a navigation list to the left with,
WinChrome.Win7Demo
Hope this helps
All,
I have searched extensively for the solution here but I have a feeling my problem stems from a basic lack of knowledge about WPF. I am new to it and have thus far hacked and Googled my way through as best I can.
Basically, I have a Ribbon dynamically interacting with a TabControl. The Ribbon tabs select a category of items, the MenuItems in the RibbonGroups then choose an item within the category. Upon clicking an item, the tabs on the TabControl need to dynamically change. Whether that is just the Headers, the tabs themselves, or the entire TabControl is fine with me. Thus far, upon clicking a MenuItem on the inside one of the RibbonGroups, I attempt to just set the Header text equal to "blah" for each tab on the TabControl. Then the Header object throws a null pointer. This is what happens whether I set the Header, the Tabs, or the TabControl itself.
Why?!?!?!?
...and how in the world do I fix it???
Thanks!
WPF is designed with data/UI separation in mind. One of the reasons you're having trouble finding a solution is because what you're trying to do is a no-no; instead of programmatically changing the UI's header text, you should be changing the underlying data instead, and allowing the WPF plumbing to update how the data is displayed.
A WPF tab control can literally contain any type of object; you could fill it with integers or strings or FooBars or whatever. There's no guarantee that any of these objects will define a Header property, and it's up to the developer to configure data bindings or templates which instruct the TabControl just how a FooBar or a whatever should be displayed.
In an ideal WPF application which adheres to the MVVM design pattern, you might have your TabControl bound to a collection of view models which each define a HeaderText property. Your view models would implement the INotifyPropertyChanged interface so that when the HeaderText property was changed on the view model then the UI would get updated.
Having said all that, if you've got an existing application it may be unrealistic to rewrite it from scratch using a different design pattern, and MVVM is not easily added on to an existing code base. If you're working with simple Designer generated UI without using any data binding, then the following code does what you ask. Sometimes.
foreach(TabItem item in tabControl.Items)
item.Header = "blah";
... but as I said before, there's no guarantee that a WPF TabControl's Items collection will contain items of type TabItem, so this code is not safe.
While RogerN's answer is probably a better answer, here is a code sample that changes the text that appears on a tab:
XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TabControl Name="MyTabControl">
<TabItem Header="Tab One">
<TextBlock Text="This is tab #1." />
</TabItem>
<TabItem Header="Tab Two">
<TextBlock Text="This is tab #2." />
</TabItem>
<TabItem Header="Tab Three">
<TextBlock Text="This is tab #3." />
</TabItem>
</TabControl>
<Button Grid.Row="1" Content="Change Tab" Name="ChangeButton" Click="ChangeButton_Click" />
</Grid>
Code behind:
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
private void ChangeButton_Click(object sender, RoutedEventArgs e) {
((TabItem)MyTabControl.Items[0]).Header = "Changed!";
}
}
Try binding it to a list in the code like so:
private List<TabItem> TabItems = new List<TabItem>()
{
"Item1",
"Item2",
"Item3"
};
tabcontrol1.ItemSource = TabItems;
Then rebind it any time you want to change the items in the tabcontrol. This way you can dynamically change names and add more tab items. In doing this you'll have to programmatically add controls using the TabItem.Content property.