In my application I have used WPF TabControl I want to handle click event of the TabItem.
How do i achieve it?
You can do this by adding labels to the header property for each tabitem in the tabcontrol. Then you can set an event for the label.
xaml
<TabControl Height="100" HorizontalAlignment="Left" Name="tabControl1">
<TabItem Name="tabItem1">
<TabItem.Header>
<Label Content="tabItem1"
MouseLeftButtonDown="tabItem1_Clicked"
HorizontalAlignment="Stretch"/>
</TabItem.Header>
<Grid />
</TabItem>
<TabItem Name="tabItem2">
<TabItem.Header>
<Label Content="tabItem2"
MouseLeftButtonDown="tabItem2_Clicked"
HorizontalAlignment="Stretch"/>
</TabItem.Header>
<Grid />
</TabItem>
</TabControl>
C# / Code Behind
private void tabItem1_Clicked(object sender, MouseButtonEventArgs e)
{
//DO SOMETHING
}
private void tabItem2_Clicked(object sender, MouseButtonEventArgs e)
{
//DO SOMETHING
}
Hope this helps.
This is an old question but I find an answer after being in the same situation.
I use SelectionChanged event on the TabControl (XAML)
<TabControl SelectionChanged="TabControl_SelectionChanged">
Code behind (C#):
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//Stuff
}
This is not the click HimSelf but its working for refreshing stuff..
You can use SelectionChanged event in TabControl and use switch case to do anything you like.
// XAML Code
<TabControl SelectionChanged="TabControl_SelectionChanged">
<TabItem Header="Item1"></TabItem>
<TabItem Header="Item2"></TabItem>
<TabItem Header="Item3"></TabItem>
</TabControl>
// Behind Code
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
string tabItem = ((sender as TabControl).SelectedItem as TabItem).Header as string;
switch(tabItem)
{
case "Item1":
break;
case "Item2":
break;
case "Item3":
break;
default:
return;
}
}
Note that, although the answer provided by d.moncada and others addresses the question, the question itself might not be what is intended. Knowing when the user clicks on a tab is different from knowing when the user makes a tab frontmost - it can be done by clicking on a tab, but it can also be achieved by other means. For example, if you click in the tab that is already frontmost, then use the left/right arrows, you can bring another tab to the front without mouse clicking - the mouse-left-button-down event does not get called in this case (as you would expect).
Wrap the header in a no-template button.
If you use the ItemsSource:
<TabControl ItemsSource="{Binding Data}">
<TabControl.ItemTemplate>
<DataTemplate>
<Button Click="Tab_Click">
<Button.Template>
<ControlTemplate>
<ContentPresenter />
</ControlTemplate>
</Button.Template>
<Button.Content>
<!-- Actual header goes here -->
</Button.Content>
</Button>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
If you have static content you can insert it into the header right away:
<TabControl>
<TabItem>
<TabItem.Header>
<Button Click="Tab_Click">
<Button.Template>
<ControlTemplate>
<ContentPresenter />
</ControlTemplate>
</Button.Template>
<Button.Content>
<!-- Actual header goes here -->
</Button.Content>
</Button>
</TabItem.Header>
</TabItem>
</TabControl>
You can do this by adding labels to the header property for each
tabitem in the tabcontrol. Then you can set an event for the label.
The solution works pretty well; however, the "label" has margin properties which can prevent the "MouseLeftButtonDown" handler from being fired unless the user clicks the label dead on. Additionally, it looks like the styling for the rest of the tabs are affected due to label padding.
You could alleviate this by overriding the default margin/padding properties of the label...or even more simply, using TextBlock.
<TabItem x:Name="tabItem1" >
<TabItem.Header>
<TextBlock MouseLeftButtonDown="tabItem1_Click">
Click Me
</TextBlock>
</TabItem.Header>
...
</TabItem>
try to find solution with GotFocus event.
private void addTabPage_GotFocus(object sender, RoutedEventArgs e)
{
addTabPage(); //method for adding page
TabControlPages.SelectedIndex = TabControlPages.Items.Count - 1; //select added page
TabControlPages.Focus(); //change forcus to selected page
}
also method for adding page (just example)
private void addTabPage()
{
TabItem tc = new TabItem();
tc.Header = "New page";
TabControlPages.Items.Insert(TabControlPages.Items.Count - 1, tc); //insert new page
}
hope this will be helpfull
Related
I have created a couple of UserControl views and now I want to show the corresponding view when a tab item is clicked. So one tab item gets one view. I would like to do this in MVVM but don't know how.
Please take a look at the following code and give me some advice on how to achieve that:
The MainView (with the TabControl only):
...
<TabControl Name="pnlFormButtons"
Margin="25"
Background="Black"
SelectedItem="{Binding SelTab}"
>
<TabItem Name="tabInventurartikel" Header="Inventurartikel hinzufügen"
Background="BlanchedAlmond" Foreground="Black"
FontFamily="Verdana"
BorderBrush="Black"
>
</TabItem>
<TabItem Name="tabSonderartikel" Header="Sonderartikel hinzufügen"
Background="BlanchedAlmond" Foreground="Black"
FontFamily="Verdana"
BorderBrush="Black"
BorderThickness="2">
</TabItem>
<TabItem Name="tabAnlegen" Header="Lieferschein anlegen"
Background="BlanchedAlmond" Foreground="Black"
FontFamily="Verdana"
BorderBrush="Black"
BorderThickness="2"
IsEnabled="False">
</TabItem>
<TabItem Name="tabDrucken" Header="Lieferschein drucken"
Background="BlanchedAlmond" Foreground="Black"
FontFamily="Verdana"
BorderBrush="Black"
BorderThickness="2"
IsEnabled="False">
</TabItem>
<TabItem Name="tabHilfeseite" Header="Hilfeseite aufrufen"
Background="BlanchedAlmond" Foreground="Black"
FontFamily="Verdana"
BorderBrush="Black"
BorderThickness="2"
IsEnabled="False">
</TabItem>
<TabItem Name="tabFehlerMelden" Header="Fehler bzw. Bug melden"
Background="BlanchedAlmond" Foreground="Black"
FontFamily="Verdana"
BorderBrush="Black"
BorderThickness="2"
IsEnabled="False">
</TabItem>
</TabControl>
...
The MainViewModel (only relevant code):
...
//Binding Property SelTab - It binds to the selected tab item
private string _selTab;
public string SelTab
{
get { return _selTab; }
set
{
_selTab = value;
OnPropertyChanged("SelTab"); //INotifyPropertyChanged
GetSelTab(); //check which tab item is selected and display the corresponding view
}
}
public void GetSelTab()
{
UserControl usc = null; //initialize user control object
switch(SelTab) //which tab item is selected?
{
case "tabInventurartikel": // = TabControl.SelectedItem
usc = new Inventurartikel(); //Initialize (Show) Inventurartikel.xaml
SelTab.Content = usc; //Here I don't know how to actually show the view in the tab item because SelectedItem.Content does not exist...
break;
case "tabSonderartikel":
usc = new neuerArtikel(); //same problem here...
break;
default:
break;
}
}
...
NOTE:
The views for the tab items are basically just user control forms that I want to show inside the tab item when the corresponding tab item is selected.
I shouldn't post them here because I want to keep the focus on the actual problem as simple and as clear as possible. Any help is highly appreciated!
The easiest solution would be to bind the tab control's item source to a list of view models. Then, if you add/remove view models, tabs are added/removed accordingly.
Main window xaml:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="CustomHeaderTemplate">
<Label Content="{Binding TabName}" />
</DataTemplate>
</Grid.Resources>
<TabControl x:Name="tbCtrl" ItemsSource="{Binding Items}" Loaded="tbCtrl_Loaded" SelectionChanged="tbCtrl_SelectionChanged" ItemTemplate="{StaticResource CustomHeaderTemplate}">
<TabControl.ContentTemplate>
<DataTemplate>
<uc:DeviceTab/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
The important thing is the binding of ItemSource.
Tab control view model:
class TabControlViewModel
{
public ObservableCollection<ItemViewModel> Items { get; } = new ObservableCollection<ItemViewModel>();
}
Tab control code behind Loaded event. Here you can add view models and the tab control sets up the tabs accordingly:
private void tbCtrl_Loaded(object sender, RoutedEventArgs e)
{
var tabControlViewModel = new TabControlViewModel();
tabControlViewModel.Items.Add(new ItemViewModel());
DataContext = tabControlViewModel;
tbCtrl.SelectedIndex = 0;
}
This only works if all tabs are the same. There's also a solution if you need different user controls for each tab. In that case, you need to specify a data template for the tab item's content. Basically you can tell it to load user controls based on the type of the view model. Unfortunately I don't know how to do that, but I've seen examples for it. I know it's not the exact answer you need, but I hope it helps!
Assume there are tab1 and tab2 as two tab items and currently active tab is tab1. SelectedIndex Property set is only called when there is a change in SelectedIndex. I want an which executes even on currently active tab. MouseDown event is not working on tab item.
Any other solutions?
I suggest you to make a Tab HeaderTemplate to handle the click. Look at the sample code below:
<Window.Resources>
<DataTemplate x:Key="myTabHeaderTemplate">
<!-- Handle click here -->
<Grid MouseDown="TabControl_MouseDown">
<TextBlock Text="{Binding}" Margin="5" />
</Grid>
</DataTemplate>
</Window.Resources>
<TabControl
Background="WhiteSmoke"
SelectedItem="{Binding CurrentTab}">
<TabItem Header="tab 1" Background="LightPink" HeaderTemplate="{StaticResource myTabHeaderTemplate}" />
<TabItem Header="tab 2" Background="LightGreen" HeaderTemplate="{StaticResource myTabHeaderTemplate}" />
</TabControl>
TabControl_MouseDown() will be invoked even if your tab is already selected
You have to implement this method in code behind:
private void TabControl_MouseDown(object sender, MouseButtonEventArgs e){ ... }
Or you can bind it to a command in your ViewModel...
I'm trying to find a good way of calling a method of the page being selected in a TabControl. Here's what I'm working with:
<TabControl x:Name="TabC" SelectionChanged="TabC_SelectionChanged">
<TabItem Header="Home" x:Name="HomeTab" IsSelected="True">
<TabItem.Content>
<Frame Source="HomePage.xaml"/>
</TabItem.Content>
</TabItem>
<TabItem Header="Test" x:Name="TestTab">
<TabItem.Content>
<Frame Source="TestPage.xaml"/>
</TabItem.Content>
</TabItem>
<TabItem Header="Test1" x:Name="Test1Tab">
<TabItem.Content>
<Frame Source="Test1Page.xaml"/>
</TabItem.Content>
</TabItem>
</TabControl>
So on my Homepage.xaml.cs, for example, I want a method to be called when I click the tabitem corresponding to it's page. How would you do this?
Any ideas are welcome!
You could subscribe to the OnLoaded event in each of your user controls.
The OnLoaded event fires each time you switch tabs.
XAML
<UserControl x:Class="WpfApplication.HomePage" Loaded="HomePage_OnLoaded" ... >
Code-behind:
private void HomePage_OnLoaded(object sender, RoutedEventArgs e)
{
// Something to do each time the tab is clicked and the page is "loaded"
}
I want to create a user control that contains a TextBlock and a StackPanel that will allow the user to add his/her own controls to the user control dynamically in XAML.
Here is the sample XAML for my UserControl:
<UserControl x:Class="A1UserControlLibrary.UserControlStackPanel"
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"
mc:Ignorable="d"
d:DesignHeight="200" d:DesignWidth="300">
<StackPanel>
<TextBlock Text="I want the user to be able to add any number of controls to the StackPanel below this TextBlock."
FontFamily="Arial" FontSize="12" FontWeight="DemiBold" Margin="5,10,5,10" TextWrapping="Wrap"/>
<StackPanel>
<!-- I want the user to be able to add any number of controls here -->
</StackPanel>
</StackPanel>
</UserControl>
I would like the user to be able to embed this user control in their XAML and add their own controls to the stack panel of the user control:
<uc:A1UserControl_StackPanel x:Name="MyUserControl_Test" Margin="10" Height="100">
<Button Name="MyButton1" Content="Click" Height="30" Width="50"/>
<Button Name="MyButton2" Content="Click" Height="30" Width="50"/>
<Button Name="MyButton3" Content="Click" Height="30" Width="50"/>
</uc:A1UserControl_StackPanel>
Doing this using the above XAML does not work. Any ideas?
You can do that, although not quite like your example. You need two things. The first is to declare a DependencyProperty of type UIElement, of which all controls extend:
public static DependencyProperty InnerContentProperty = DependencyProperty.Register("InnerContent", typeof(UIElement), typeof(YourControl));
public UIElement InnerContent
{
get { return (UIElement)GetValue(InnerContentProperty); }
set { SetValue(InnerContentProperty, value); }
}
The second is to declare a ContentControl in the XAML where you want the content to appear:
<StackPanel>
<TextBlock Text="I want the user to be able to add any number of controls to the StackPanel below this TextBlock."
FontFamily="Arial" FontSize="12" FontWeight="DemiBold" Margin="5,10,5,10" TextWrapping="Wrap"/>
<StackPanel>
<ContentControl Content="{Binding InnerContent, RelativeSource={RelativeSource AncestorType={x:Type YourXmlNamspacePrefix:ContentView}}}" />
</StackPanel>
</StackPanel>
In my opinion, if you use StackPanels, you could find that your content does not get displayed correctly... I'd advise you to use Grids for layout purposes for all but the simplest layout tasks.
Now the one difference to your example is in how you would use your control. The InnerContent property is of type UIElement, which means that it can hold one UIElement. This means that you need to use a container element to display more than one item, but it has the same end result:
<YourXmlNamspacePrefix:YourControl>
<YourXmlNamspacePrefix:YourControl.InnerContent>
<StackPanel x:Name="MyUserControl_Test" Margin="10" Height="100">
<Button Content="Click" Height="30" Width="50"/>
<Button Content="Click" Height="30" Width="50"/>
<Button Content="Click" Height="30" Width="50"/>
</StackPanel>
</YourXmlNamspacePrefix:YourControl.InnerContent>
</YourXmlNamspacePrefix:YourControl>
And the result:
UPDATE >>>
For the record, I know exactly what you want to do. You, it seems, do not understand what I am saying, so I'll try to explain it one last time for you. Add a Button with the Tag property set as I've already shown you:
<Button Tag="MyButton1" Content="Click" Click="ButtonClick" />
Now add a Click handler:
private void ButtonClick(object sender, RoutedEventArgs e)
{
Button button = (Button)sender;
if (button.Tag = "MyButton1") DoSomething();
}
That's all there is to it.
I have a data-bound TreeView with a WindowsFormsHost in the data template of its items. The more items in the TreeView, and so the more WindowsFormsHost in it, the slower the UI becomes.
The TreeView is in a TabItem, itself in a TabControl. The sluggishness is most obvious whenever the TabItem is selected (ie when I switch from another TabItem to the TabItem with the TreeView).
To simplify, I made a simpler app with a ListBox instead of a TreeView:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<TabControl>
<TabItem Header="Tab 1">
<StackPanel>
<Button Content="Add 50 WindowsFormsHost controls"
Click="Button_Click" />
<ListBox Name="lst" Height="300">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Bar}" />
<WindowsFormsHost />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</TabItem>
<TabItem Header="Tab 2" />
</TabControl>
</Window>
With this in the form's code:
class Foo { public string Bar { get { return DateTime.Now.Ticks.ToString(); } } }
void Button_Click(object sender, RoutedEventArgs e)
{
if (lst.ItemsSource == null)
lst.ItemsSource = new ObservableCollection<Foo>();
for (int j = 0; j < 50; j++)
(lst.ItemsSource as IList<Foo>).Add(new Foo());
}
After clicking the button, scrolling the ListBox's content becomes less fluid, and there is a delay when switching back to Tab 1.
Any ideas on why this is happening, and on if there is anything to do about it?
Instead of hosting multiple WindowsFormsHost in a WPF ListBox's items, why cant you host one WinForm ListBox having above items in a single WindowsFormsHost? This would not only make the scrolling easy (due to entirely in WinForms UI context) but also make your WPF app memory efficient (due to single WindowsFormsHost).
If this is not what you are for, then try to loose the Virtualisation of your ListBox's Items Panel (which by default is a VirtualizedStackPanel).
Let me know if this helps?