Add tabs to an existing tab control in WPF C# - c#

I am trying to add Tabs to a tab control in WPF but nothing appears on the control at runtime. I have tried following the examples I keep seeing. Right now this is what I have but it isn't working
_myConnection.Open();
SqlDataReader myReader = myCommand.ExecuteReader();
while (myReader.Read())
{
MessageBox.Show(myReader["SectionName"].ToString());
TabItem newTabItem = new TabItem
{
Header = myReader["SectionName"].ToString(),
Name = myReader["SectionID"].ToString()
};
TabMain.Items.Add(newTabItem);
}
_myConnection.Close();
TabMain.SelectedIndex = 0;

You can add tabs dynamically by using the following code.
Add the following code to declare tab control instance globally.
TabControl tbControl;
Now, add the following code to the loaded event of the tab control.
private void tbCtrl_Loaded(object sender, RoutedEventArgs e)
{
tbControl = (sender as TabControl);
}
I have used a button to add new tabs for the existing tab control.
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
TabItem newTabItem = new TabItem
{
Header = "Test",
Name = "Test"
};
tbControl.Items.Add(newTabItem);
}
Following is my tab control xaml view.
<TabControl x:Name="tbCtrl" HorizontalAlignment="Left" Height="270" Margin="54,36,0,0" VerticalAlignment="Top" Width="524" Loaded="tbCtrl_Loaded">
<TabItem Header="Tab - 01">
<Grid Background="#FFE5E5E5">
<Button x:Name="btnAdd" Content="Add New Tab" HorizontalAlignment="Left" Margin="68,95,0,0" VerticalAlignment="Top" Width="109" Height="29" Click="btnAdd_Click"/>
</Grid>
</TabItem>
</TabControl>
Finally, using this you can add any amount of tabs dynamically to the existing tab control.
Hope this fulfill your need.

Perhaps something in your DB values? I just wrote the most trivial of for loops to test, and this works fine (using just a TabControl and OnLoaded event on the XAML):
private void Window_Loaded(object sender, RoutedEventArgs e)
{
for (int i = 1; i <= 3; i++)
{
var item = new TabItem {Header = i.ToString(), Name = $"tab{i}"};
TabMain.Items.Add(item);
}
}

Related

Why does a dynamic TabControl seem to be unable to switch between the tabs?

I'm using a dynamic TabControl in a page:
<TabControl x:Name="DynamicTabControl"
ItemsSource="{Binding TabsCollection, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TabView}}}"
SelectionChanged="tabDynamic_SelectionChanged">
<TabControl.ItemTemplate>
<DataTemplate x:Name="TabHeader" DataType="TabItem">
<DockPanel Width="30">
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=TabItem }, Path=Header}" />
</DockPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate >
<local:TabItem/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
public ObservableCollection<TabItem> TabsCollection {get;set;}
private TabItem _tabAdd;
#region Initialization
public TabView()
{
InitializeComponent();
TabsCollection = new ObservableCollection<TabItem>();
TabsCollection.Add(new TabItem { Header = "Tab 1"});
// add a tabItem with + in header
_tabAdd = new TabItem
{
Header = "+"
};
TabsCollection.Add(_tabAdd);
DynamicTabControl.DataContext = TabsCollection;
DynamicTabControl.SelectedIndex=0;
}
#endregion
private TabItem AddSubsequentTabItem()
{
int count = TabsCollection.Count;
TabItem tab = new TabItem();
tab.Header = string.Format("Tab {0}", count);
tab.Name = string.Format("tab{0}", count);
TabsCollection.Insert(count - 1, tab);
return tab;
}
private void tabDynamic_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
TabItem tab = DynamicTabControl.SelectedItem as TabItem;
if (tab == null) return;
if (tab.Equals(_tabAdd))
{
// clear tab control binding
DynamicTabControl.DataContext = null;
TabItem newTab = this.AddSubsequentTabItem();
// bind tab control
DynamicTabControl.DataContext = TabsCollection;
// select newly added tab item
DynamicTabControl.SelectedItem = newTab;
}
else
Dispatcher.BeginInvoke((Action)(() => DynamicTabControl.SelectedIndex = DynamicTabControl.SelectedIndex));
}
But, I'm unable to get the tab control to switch between my TabItems.
I'm using an amalgamation of the dynamic tab control from https://www.codeproject.com/Articles/493538/Add-Remove-Tabs-Dynamically-in-WPF , with modifications to create the GUI objects from within xaml.
My title pretty much says it all: Why is my TabControl not switching tabs? Even besides fixing the problem, I'd like to know if subscribing a method to the "selectionChanged" event unsubscribes the native tab switching.
Here is the barebones example project of the problem

WPF - How to persist current content when setting the content property

I'm facing a problem where by setting the content property of my window I obviously remove pre-existing content. On all windows I have a dockpanel that I use to pop up help contextual help to the user but this is lost when I set the content property of the window. Therefore I will only see the content for the control I've added and pressing F1 does nothing as the dockpanel does not exist. I don't want to add this dockpanel to every control as it's poor code-reuse so what can I do to keep the dockpanel on the window and add content without overwriting original content of the window?
This is the code where I set the content of the window.
private void btnHelp_Click(object sender, RibbonControlEventArgs e)
{
System.Windows.Window window = new ResizeableWindow()
{
Title = "Help",
Content = new Controls.Help(),
ResizeMode = ResizeMode.NoResize
};
window.ShowDialog();
}
This is code for my Help control it's just a document viewer to read an xps document, this is used by the dockpanel.
public partial class Help : UserControl
{
public Help()
{
InitializeComponent();
string appPath = "path";
XpsDocument doc = new XpsDocument(appPath, FileAccess.Read);
var docx = doc.GetFixedDocumentSequence();
HelpDocViewer.Document = docx;
}
}
This is the xaml of my ResizableWindow containing the Dockpanel
<Window x:Class="Controls.ResizeableWindow"
KeyDown="HelpKeyListen">
<Grid>
<DockPanel x:Name="HelpPanel">
</DockPanel>
</Grid>
</Window>
Here is the code for the resizeable window
public ResizeableWindow()
{
InitializeComponent();
}
private void HelpKeyListen(object sender, KeyEventArgs e)
{
if (e.Key == Key.F1)
{
var HelpControl = new Help();
DockPanel.SetDock(HelpControl, Dock.Right);
HelpPanel.Children.Insert(0, HelpControl);
}
}
Use Placeholders inside the DockPanel instead of replacing the window content:
<DockPanel x:Name="HelpPanel">
<ContentControl x:Name="HelpContent" DockPanel.Dock="Right"/>
<ContentControl x:Name="MainContent"/>
</DockPanel>
Then assign the contents of the contentcontrols as needed
private void HelpKeyListen(object sender, KeyEventArgs e)
{
if (e.Key == Key.F1)
{
HelpContent.Content = new Help();
}
}
Possibly create a new dependency property in ResizeableWindow if you want to provide main content from the outside. Lets say you add a dependency property (visual studio code snipped propdp) named MainContent, then you can bind it as follows:
<DockPanel x:Name="HelpPanel">
<ContentControl x:Name="HelpContent" DockPanel.Dock="Right"/>
<ContentControl x:Name="MainContentPlaceholder" Content="{Binding MainContent,RelativeSource={RelativeSource AnchestorType=Window}}"/>
</DockPanel>
The more appropriate option would be to replace the MainContentPlaceholder by some more WPF/MVVM friendly way to display your contents, but thats out of scope for the question.

Access Datacontext from binded user control

I have build a dynamic UserControl from an ObservableCollection as follows...
public static ObservableCollection<Model.Model.ControleData> ListControleMachine = new ObservableCollection<Model.Model.ControleData>();
public Genkai(string Autorisation) {
InitializeComponent();
DataContext = this;
icTodoList.ItemsSource = ListControleMachine;
Model.Model.ControleData v = new Model.Model.ControleData();
v.ComputerName = "M57095";
v.ImportSource = "LOAD";
ListControleMachine.Add(v);
}
XAML
<ItemsControl x:Name="icTodoList" ItemsSource="{Binding ListControleMachine}" >
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:ControlMachineII}">
<local:ControlMachineII />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
But how can I access the DataContext from C# code?
For example say I want to delete the UserControl with a close button in itself, I need at least access ControleData.ComputerName value then remove it from Mainform.ListControleMachine.
I can't find the best practice for achieve this and play with my data in UserControl code.
The remove button code is like this i think (with hard coded value)
Genkai.ListControleMachine.Remove(Genkai.ListControleMachine.Where(X => X.ComputerName == "M57095").Single());
i finaly found that my DataContext was not yet initialized at start that why i got error so i had to wait for the datacontext first: here code for correction
public ControlMachineII()
{
InitializeComponent();
DataContextChanged += new DependencyPropertyChangedEventHandler(ControlMachineII_DataContextChanged);
}
private void ControlMachineII_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
string compname = (this.DataContext as Model.Model.ControleData).ComputerName;
Console.WriteLine("DataContext initialized computername :" +compname);
}
I saw you posted same question today with some more data. I'm going to present the solution using that data.
Solution 1 :
Use Tag property of button like below:
<Button Content="Close this UC" HorizontalAlignment="Left" Margin="414,22,0,0"
VerticalAlignment="Top" Width="119" Click="Button_Click" Tag="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
Event handler:
private void Button_Click(object sender, RoutedEventArgs e)
{
var button = sender as Button;
List<object> list = (button.Tag as ItemsControl).ItemsSource.OfType<TodoItem>().ToList<object>();
list.Remove(button.DataContext);
(button.Tag as ItemsControl).ItemsSource = list;
}
Solution 2:
More elegant solution:
Create this Style in your MainWindow:
<Window.Resources>
<Style TargetType="Button">
<EventSetter Event="Click" Handler="Button_Click"/>
</Style>
</Window.Resources>
So now the Handler of any Button Click event in any MainWindow's descendant Button Control is in the MainWindow.xaml.cs.
Then place the handler method in MainWindow.xaml.cs and change the handler like below:
private void Button_Click(object sender, RoutedEventArgs e)
{
var button = sender as Button;
items.Remove(button.DataContext as TodoItem);
icTodoList.ItemsSource = null;
icTodoList.ItemsSource = items;
}

Adding an click event to a programatically added menu item

I am working on a C# WPF project and I am storing some items in an SQLite database, when the program loads, it then retrieves the items from the database and adds the items to the menu. What I then need to do is to allow the user to click on one of the added menu items and something is done based on what was clicked. I can't find anything on how to do this, below is the code for how I am adding the menu item to the menu programatically.
StoredDBConnectionManager storedDbConnectionManager = new StoredDBConnectionManager(Properties.Settings.Default.app_dbPassword);
List<string> connections = storedDbConnectionManager.getStoredConnections();
foreach (string connection in connections)
{
mnuFileDBConnections.Items.Add(connection);
}
Thanks for any help you can provide.
Here's an example:
XAML:
<Menu Height="23" HorizontalAlignment="Left" Name="menu1" VerticalAlignment="Top" Width="200" />
Code behind:
public MainWindow() {
InitializeComponent();
MenuItem item = new MenuItem { Header = "test" };
item.Click += new RoutedEventHandler(item_Click);
menu1.Items.Add(item);
}
public void item_Click(Object sender, RoutedEventArgs e) {
MessageBox.Show("Hello!");
}
There should be a MenuItem control you can instantiate and use the connection as its Header or Content.
MenuItem will then have a Click event handler against it or you can set the command.
Ideally however, you should be retrieving the connections collection, setting it to a property on your model and then have the menu bound to that collection, that way it's a simple matter of making use of an ItemTemplate for the menu.
e.g.
StoredDBConnectionManager storedDbConnectionManager = new StoredDBConnectionManager(Properties.Settings.Default.app_dbPassword);
List<string> connections = storedDbConnectionManager.getStoredConnections();
foreach (string connection in connections)
{
var mi = new MenuItem()
{
Header = connection,
};
mi.Click += ConnectionMenuItemClicked;
mnuFileDBConnections.Items.Add(mi);
}
OR with binding:
<Menu ItemsSource="{Binding Connections}">
<Menu.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding}" Click="ConnectionsMenuItem_Clicked">
</MenuItem>
</DataTemplate>
</Menu.ItemTemplate>
</Menu>
foreach(string menuCaption from ...)
{
MenuItem mi=new MenuItem();
mi.Header = meniCaption;
mi.Click += (s,e) =>
{
...
}
}

WPF - How do determine the index of the current item in a listbox from button handler

i have a listbox with a data template that contains a button.
When the button is clicked I want to get in the button
click handler the index of the listbox item that was current??
How do I do this please?
Malcolm
More appropriate answer,
private void Button_Click(object sender, RoutedEventArgs e)
{
DependencyObject dep = (DependencyObject)e.OriginalSource;
while ((dep != null) && !(dep is ListViewItem))
{
dep = VisualTreeHelper.GetParent(dep);
}
if (dep == null)
return;
int index = lstBox.ItemContainerGenerator.IndexFromContainer(dep);
}
Hope the bellow code will help you.
private void Button_Click(object sender, RoutedEventArgs e)
{
var b = (Button)sender;
var grid = (Grid)b.TemplatedParent
var lstItem = (ListBoxItem)grid.TemplatedParent;
int index = lstBox.ItemContainerGenerator.IndexFromContainer(lstItem);
// rest of your code here...
}
And the XAML for the above assumed to be a DataTemplate on a ListBox named lstBox:
<DataTemplate x:Key="template">
<Grid>
<Button Click="Button_Click" Content="Press"/>
</Grid>
</DataTemplate>
Have you checked the "SelectedIndex" property of the listbox? It might be set by clicking on the button.
Probably way to late but using "IndexOf" on the listbox "Items" will give you the index #
Regards
myListbox.Items.CurrentItem seems be what you are looking for.
Hi you can use ContentPresenter.Content to get current item , instead of current index:
<DataTemplate DataType="{x:Type MyModel}">
<StackPanel Orientation="Horizontal" Margin="0 5">
<TextBlock Text="{Binding Title}" />
<Button Content="Active" Click="Button_Click" />
</StackPanel>
</DataTemplate>
and in code:
private void Button_Click(object sender, RoutedEventArgs e)
{
var button = e.Source as Button;
var contentPresenter = button.TemplatedParent as ContentPresenter;
var myModel = (MyModel)contentPresenter.Content;
}

Categories