I have Xaml WPF window with TabControl inside. Each TabItem of it contains a page inside frame like this:
<TabItem >
<TabItem.Header>
Users
</TabItem.Header>
<Frame Source="/Views/UsersPage.xaml" />
</TabItem>
<TabItem>
<TabItem.Header>
Stats
</TabItem.Header>
<Frame Source="/Views/OrdersPage.xaml"/>
</TabItem>
The problem is when i open this window all pages inside frames loads simultaneously, so if , eg, I put MessageBox on each page load i got a lot of them. I want my pages to load only when I click on corresponding tab header.
How can i achieve this?
One possible solution would be to inherit from TabItem like this:
public class MyCustomTabItem : TabItem
{
public void MyCustomInitFunction()
{
//Do all your stuff here
}
}
In there put all your stuff that is supposed to be happening when the control gets clicked.
Create your TabControl with those new items:
<TabControl SelectionChanged="myTabControl_SelectionChanged" Name="myTabControl">
<MyCustomTabItem Name="myTab1">
<MyCustomTabItem.Header>
Users
</MyCustomTabItem.Header>
<Frame Source="/Views/UsersPage.xaml" />
</MyCustomTabItem>
<MyCustomTabItem Name="myTab2">
<MyCustomTabItem.Header>
Stats
</MyCustomTabItem.Header>
<Frame Source="/Views/OrdersPage.xaml"/>
</MyCustomTabItem>
</TabControl>
Hookup the SelectionChanged event handler to call your special init function:
private void myTabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
TabControl tc = (TabControl)e.Source;
if(tc != null)
{
MyCustomTabItem ti = (MyCustomTabItem)tc.Items[tc.SelectedIndex];
if(ti != null)
{
ti.MyCustomInitFunction()
}
}
}
If you only want this to happen the first time it get's shown put a bool variable in your MyCustomTabItem class that keeps track if it has already happened once, and if so just return instead of running it again.
Related
So I have WPF window with one tabcontrol which has 4 tabitems.
When placing a datagrid in first tab item, and binding to datatable,it works fine like so:
private void LoadServices()
{
if (serviceData.GetAllServices(currentID) != null)
{
dataGridServices.ItemsSource = serviceData.GetAllServices(currentID).DefaultView;
dataGridServices.Columns[0].Visibility = Visibility.Hidden;
}
}
(btw I am calling the method above in Window_ContentRendered event and it works fine).
I then add another datagrid in the second tabitem with the EXACT same code but now I get the error on the line below: "Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index')'":
dataGridServices.Columns[0].Visibility = Visibility.Hidden;
When debugging and hovering over the ItemSource property, I can see that there are three columns there, so how can it be index out of range? By the way the method for binding to the second datagrid is the same as above and I am also calling this from Window_ContentRendered event too.
(Just to be clear, when I am not hiding the specific column, the data loads fine with the column names too which are defined in stored procedure)
Is there something unique in the way datagrids and tabcontrols work together in WPF?
LATEST CODE:
XAML:
<TabControl x:Name="tabControl">
<TabItem x:Name="FirstTabItem" Header="First">
<DataGrid x:Name="datagridServicesFirstTab">
</DataGrid>
</TabItem>
<TabItem x:Name="SecondTabItem" Header="Second">
<DataGrid x:Name="datagridServices" AutoGeneratedColumns="OnLoaded" >
</DataGrid>
</TabItem>
</TabControl>
Event Handler Method:
private void OnLoaded(object sender, EventArgs e)
{
LoadServices();
}
Data Method:
private void LoadServices()
{
if (serviceData.GetAllServices(currentID) != null)
{
datagridServices.ItemsSource = serviceData.GetAllServices(currentID).DefaultView;
datagridServices.Columns[0].Visibility = Visibility.Hidden;
}
}
When debugging and hovering over the ItemSource property, I can see that there are three columns there, so how can it be index out of range?
Probably because the columns of the DataGrid has not yet been generated. The content of a tab item is not rendered until you select that tab.
Move your code from Window_ContentRenderer to a AutoGeneratedColumns event handler for the second DataGrid:
private void OnLoaded(object sender, EventArgs e)
{
LoadServices();
}
XAML:
<TabItem Header="second...">
<DataGrid ... AutoGeneratedColumns="OnLoaded" />
I am wondering how to switch to a different tab within a tab control.
I have a main window that has a tab control associated with it and it directs to different pages. I want to switch to a tab from an event triggered within a different tab. When I try to use TabControl.SelectedIndex I get the error "An object reference is required to access non-static, method or property 'MainWindow.tabControl'
Here is my code declaring the TabControl from the MainWindow and trying to switch to it from a different tab.
<TabControl Name="tabControl" Margin="0,117,0,0" SelectionChanged="tabControl_SelectionChanged" Background="{x:Null}" BorderBrush="Black">
<TabItem x:Name="tabMO" Header="MO" IsTabStop="False">
<Viewbox x:Name="viewMO" Margin="0,0,0,0" Stretch="Fill" StretchDirection="Both">
<local:ManufacturingOrder x:Name="mo" Height="644" Width="1322"/>
</Viewbox>
</TabItem>
<TabItem x:Name="tabOptimize" Header="Optimize" IsTabStop="False">
<Viewbox x:Name="viewOptimize" Margin="0,0,0,0" Stretch="Fill" StretchDirection="Both">
<local:EngineeringOptimization x:Name="Optimize" Height="644" Width="1600"/>
</Viewbox>
</TabItem>
</TabControl>
private void dataGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var cellInfo = dataGrid.SelectedCells[0];
var content = (cellInfo.Column.GetCellContent(cellInfo.Item) as TextBlock).Text;
var r = new Regex("[M][0-9]{6}");
if (r.IsMatch(content.ToString()))
{
MainWindow.tabControl.SelectedIndex = 4;
}
}
I have tried switching this to a private static void and received the same error.
I have also tried the following code, creating an instance of MainWindow, and there is no errors but when I run the code the selected tab doesn't change on the screen. But if I use a MessageBox to view the Selected Index, than I see my changed tab Index.
private void dataGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var cellInfo = dataGrid.SelectedCells[0];
var content = (cellInfo.Column.GetCellContent(cellInfo.Item) as TextBlock).Text;
var r = new Regex("[M][0-9]{6}");
if (r.IsMatch(content.ToString()))
{
MainWindow frm = new MainWindow();
frm.tabControl.SelectedIndex = 4;
}
}
It looks like your main problem is that you do not have easy access to your MainWindow and all of its children from within your ManufacturingOrder or EngineeringOptimization UserControls. Which is normal. There are a few ways around this. A simple one, which violates some MVVM principles, (but you're doing that anyway, so I don't think you'll mind) is to retrieve the instance of your MainWindow object:
//Loop through each open window in your current application.
foreach (var Window in App.Current.Windows)
{
//Check if it is the same type as your MainWindow
if (Window.GetType() == typeof(MainWindow))
{
MainWindow mWnd = (MainWindow)Window;
mWnd.tabControl.SelectedIndex = 4;
}
}
Once you retrieve the running instance of your MainWindow, then you have access to all its members. This has been tested as well as possible without access to your specific custom UserControls and instances. But it's a pretty standard problem and solution.
You were on the right track with your last bit of code in your question, but you were creating a 'new' instance of your MainWindow. You have to retrieve the current running instance, not a new instance.
I am using a WPF tabcontrol to display items which are bound from a viewmodel.
By default on start the first item of the list is selected but I want no item to be selected on start. I can set the SelectedItem in the OnSelectionChanged event to null then no item is selected on start but then it is no longer possible to manually select a item.
public partial class ProjectScopeMain : Window
{
private bool firstStart = true;
public ProjectScopeMain()
{
this.Initialized += this.ProjectScopeMain_Initialized;
this.InitializeComponent();
}
private void ProjectScopeMain_Initialized(object sender, System.EventArgs e)
{
this.TabControlSettings.SelectionChanged += TabControlSettingsOnSelectionChanged;
}
private void TabControlSettingsOnSelectionChanged(object sender, EventArgs e)
{
this.TabControlSettings.SelectedItem = null;
}
private void ButtonCreate_Click(object sender, System.Windows.RoutedEventArgs e)
{
this.Close();
}
}
My XAML Code. SelectedIndex=-1 does not work
<customControls:TabControl x:Uid="tabControlSettings" x:Name="TabControlSettings"
prism:RegionManager.RegionName="{x:Static infrastructure:RegionNames.ProjectScopeTabsRegion}"
TabStripPlacement="Left" Style="{DynamicResource TabControlStyle}"
ItemContainerStyle="{DynamicResource TabItemVerticalProjectScopeStyle}" SelectedIndex="-1"/>
I don't believe the tab control lets you have nothing selected. An easy work around for this is to create an empty tab with a collapsed visibility, and navigate to it when you would otherwise wish to clear your tab control. This will result in a tab's content being shown (which in this case is empty) and no header being present.
<TabControl Name="MyTabControl" SelectedIndex="0">
<TabItem Header="" Visibility="Collapsed">
<!--There's nothing here-->
</TabItem>
<TabItem Header="Item 1">
<TextBlock Text="Some item 1" />
</TabItem>
<TabItem Header="Item 2">
<TextBlock Text="Some item 2" />
</TabItem>
</TabControl>
You could 'clear' it with:
MyTabControl.SelectedIndex = 0;
Since you wish to bind the child items, I would imagine you will need to combine the children in a resource first.
You can deselect any TabItem by setting its IsSelected property to false. The content of TabControl will be blank once none of its TabItems are selected.
Subscribe to the Loaded event of the TabControl then set SelectedItem to null:
<TabControl Loaded="TabControl_OnLoaded">
<TabItem Header="page 1" Content="page 1" />
<TabItem Header="page 2" Content="page 2" />
</TabControl>
private void TabControl_OnLoaded(object sender, RoutedEventArgs e)
{
((TabControl)sender).SelectedItem = null;
}
It will work even if you bind SelectedItem to a property in your ViewModel, but there will be a moment after loading the page that you'll get a non-null value there, and after that null, but from what I've seen it didn't cause any weird flickering or anything so it's probably fine.
Tested on .NET Framework 4.8
You can select nothing by setting SelectedTab property nullptr.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.tabcontrol.selectedtab?view=net-5.0
A TabPage that represents the selected tab page. If no tab page is selected, the value is null.
I have a SelectionChanged event in a ListPicker within one of my application Pages that fires multiple times before the page is loaded. This is really inconvenient for me as when an item is selected, a MessageBox is displayed (and other actions will be performed). The MessageBox is displayed twice every time the page is NavigatedTo. How can I fix this?
XAML
<toolkit:ListPicker x:Name="ThemeListPicker" Header="Theme"
ItemTemplate="{StaticResource PickerItemTemplate}"
SelectionChanged="ThemeListPicker_SelectionChanged"/>
XAML.CS
private void ThemeListPicker_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
if(ThemeListPicker.SelectedIndex != -1)
{
var theme = (sender as ListPicker).SelectedItem;
if (index == 0)
{
Settings.LightTheme.Value = true;
MessageBox.Show("light");
}
else
{
Settings.LightTheme.Value = false;
MessageBox.Show("dark");
}
}
}
well, that's how a listpicker behaves, what best you can do is instead of making ThemeListPicker_SelectionChanged make a parent stackpanel inside the datatemplate somewhat like this
<Listpicker.ItemTemplate>
<DataTemplate x:Name="PickerItemTemplate">
<StackPanel tap="stk_Tap">
<TextBlock/>
</StackPanel>
</DataTemplate>
</Listpicker.ItemTemplate>
<Listpicker.FullModeItemTemplate>
<DataTemplate x:Name="PickerFullModeItemTemplate">
<StackPanel tap="stk_Tap">
<TextBlock/>
</StackPanel>
</DataTemplate>
<Listpicker.FullModeItemTemplate>
now use this tap stk_Tap to do your action as, this event would also get called every time the selection changed gets called but, it wont exhibit the buggy behavior like that of selection changed event.
hope this helps.
Attach the SelectionChanged event after the ListPicker is Loaded.
...
InitializeComponent();
YourListPicker.Loaded += YourListPicker_Loaded;
...
private void YourListPicker_Loaded(object sender, RoutedEventArgs e)
{
YourListPicker.SelectionChanged += YourListPicker_SelectionChanged;
}
I have application with toolbar (Add and Delete commands) and TabControl. There is VariableGrid control in each tabItem of TabControl.
look image at: http://trueimages.ru/view/cNFyf
<DockPanel >
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<Button Command="{x:Static VariableGrid:VariableGrid.AddRowCommand}"/>
<Button Content="Delete" Command="ApplicationCommands.Delete" />
</ToolBar>
</ToolBarTray>
<TabControl x:Name="tc">
<TabItem Header="Tab 1">
<vg:VariableGrid ItemsSource="{Binding Items1, Mode=TwoWay}"/> </TabItem>
<TabItem Header="Tab 2">
<vg:VariableGrid ItemsSource="{Binding Items2, Mode=TwoWay}"/>
</TabItem>
</TabControl>
<DockPanel >
Toolbar commands are implemented in my control:
public partial class VariableGrid : DataGrid, INotifyPropertyChanged
{
public static RoutedCommand AddRowCommand = new RoutedCommand();
public VariableGrid()
{
this.CommandBindings.Add(new CommandBinding(VariableGrid.AddRowCommand, AddRow));
this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Delete, R emoveRow, CanRemoveRow));
}
private void AddRow(object sender, ExecutedRoutedEventArgs e)
{
…
}
private void RemoveRow(object sender, ExecutedRoutedEventArgs e)
{
…
}
private void CanRemoveRow(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = (SelectedItems.Count > 0);
}
}
There are few cases when commands in toolbar are disabled:
when application is running
when I click on gray field of DataGrid
when DataGrid is empty
When any row of DataGrid is selected - commands of toolbar are becoming active.
Can you help me with my issue? What CommandTarget of toolbar buttons should I set?
PS: There are two VariableGrids in my application. Thats why I can't set CommandTarget as "{Binding ElementName=variableGrid}". I think it should be set to FocusedElement. But I don't know how to do this.
WPF should be calling your CanRemoveRow method every once in a while to check if it is ok to remove a row. You should put a Boolean condition in this method that will answer that question. If you want similar functionality for your AddRowCommand, add a CanAddRow method where you bind the AddRowCommand.
You may want to read the Commanding Overview at MSDN.
UPDATE >>>
Oh... do you want to know what code to use for these disabled conditions? I'll assume so:
when application is running
I'm guessing you mean 'when application is busy'... add a Boolean property named IsBusy, set it to true when the application performs any long running processes, then add !IsBusy into your method condition.
when I click on gray field of DataGrid
when DataGrid is empty
Both of these conditions can be judged using the SelectedItem property of the DataGrid by adding && dataGrid.SelectedItem != null into your method condition.
Therefore, you want something like the following:
private void CanRemoveRow(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = !IsBusy && SelectedItem != null);
}