I have in the mainwindow this code:
<TabControl x:Name="tc" Margin="0" SelectedIndex="0">
<TabItem Header="Tab 1" Width="150"
IsSelected="false">
<!--<TextBox Width="200" Height="200"/>-->
</TabItem>
</TabControl>
i have a mainwindowviewmodel and i want it to bind to the controltab name which is tc:
private void AddTab_Execute(object parm)
{
s = s + 1;
TabItem Item = new TabItem();
Item.Width = 150;
Item.Header = "Tab " + s;
tc.Items.Add(Item);
}
he doesn't recognize tc
what do i have to do ?
This has nothing to do with binding, but it would be easier if you had one. You should not need to reference controls like the TabControl in the view-model.
Bind the ItemsSource of the the TabControl to an ObservableCollection, then you just need to add items to that collection. Use the ItemTemplate (header) and ContentTemplate (tab item content) to create the tabs from the items in the collecton dynamically.
First of all, be careful with how you are using the word "bind". That has a particular meaning which is not what you are doing in your example. To see what binding actually means, check out this article.
You cannot access controls in your UI from your viewmodel. What you should be doing is binding the ItemsSource of the TabControl to a collection in the viewmodel. You can create DataTemplateSelectors and use them for the TabControl's ContentTemplateSelector and ItemTemplateSelector if you want to select different templates depending on the item which is bound to each TabItem.
Related
I am working on a project where I want to add button in content property of TabControl in WPF.
I tried lot many ways but I failed.
This is the code Example :
XAML File
c# File
1. XAML File
<TabControl TabStripPlacement="Left" Name="DynamicTab">
<TabControl.ItemTemplate>
<DataTemplate>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
</DataTemplate>
</TabControl.ContentTemplate>
2. C# File
foreach(DataContextClass glist in groupsList)
{
TabItem tab = new TabItem();
StackPanel sp = new StackPanel();
tab.Header = glist.ItemGroup;
DynamicTab.Items.Add(tab);
itemsList = itemsDALObj.ItemsGroupWise(glist.ItemGroup);
for(int i =0 ; i<itemsList.Count;i++)
{
Button b = new Button();
b.Name = "Button" + (i + 1);
b.Content = itemsList[i].ItemName;
b.Height = 80;
b.Width = 100;
tab.Content = sp;
sp.Children.Add(b);
}
};
I tried following options:
By adding stackpanel, Grid, Button in <DataTemplate> of <TabControl.ContentTemplate>.
By adding Dynamic Grid and in that Grid I add Dynamic Button.
Many other ways also which I am not able to explain.
You have to replace in XAML instead of <TabControl.ContentTemplate> replace it with <TabControl.DataContext> and that's the solution it takes me hours to find this little mistake.
<TabControl.DataContext>
<DataTemplate>
</DataTemplate>
</TabControl.DataContext>
The above is the change in XAML part.
A TabControl is designed around the idea that the only controls that will be added to it are TabItem controls.
You can in fact, add other controls directly to the parent TabControl object, but when you do, it automatically creates an implicit TabItem anyway to hold those objects.
For instance, if you add two Button controls directly to the TabControl, two implicit TabItem controls are created, one to hold each Button:
<TabControl>
<Button/>
<Button/>
</TabControl>
This works, but is very much the wrong way to go about it.
To properly add content to a TabControl, first create a TabItem. Add your other controls to the TabItem, and then add the TabItem to the TabControl. (You can add the TabItem to the TabControl first if you want, it doesn't really matter)
In XAML:
<TabControl>
<TabItem Header="This is the label for the tab item.">
<Button Content="My Button"/>
</TabItem>
</TabControl>
In code (assuming a pre-existing TabControl):
TabItem ti = new TabItem();
ti.Header = "Tab Header Text";
Button bt = new Button();
bt.Content = "Button Text";
ti.Content = bt;
myTabControl.Items.Add(ti);
A TabControl, like all WPF controls is a container. It can hold multiple objects, but all of those objects are actually TabItem objects. The TabItem control can only hold one object, so if you want it to contain more than just the Button, you have to add a different container to the TabItem first; like a Grid or StackPanel.
My name is Andrea this is my first post ever.
Frequently you have helped me as a simple reader, now I'm writing because I wanted to direct support.
I have to create and a tab control and with a button "Add Tab" I have to add a new tab with the same content.
Up to this everything is fine.
Within Tab I have a textedit and a combobox.
My problems are two:
1 How do I load the contents of the combobox for each tab I add?
2 Every time I write the text of and a edit tab override also edit the text of the other tab.
Here the code:
Data Template in Xaml:
<DataTemplate x:Key="tabItemContent">
<dxlc:LayoutGroup Orientation="Vertical" Header="Target Description" IsCollapsible="True">
<!--Name-->
<dxlc:LayoutItem>
<dxlc:LayoutGroup Orientation="Horizontal" ItemSpace="4" >
<dxlc:LayoutItem Label="Name" Margin="10">
<dxe:TextEdit x:Name="TextEdit_NameTarget"/>
</dxlc:LayoutItem>
</dxlc:LayoutGroup>
</dxlc:LayoutItem>
<!--Nation e Label-->
<dxlc:LayoutItem>
<dxlc:LayoutGroup Orientation="Horizontal" ItemSpace="12" >
<dxlc:LayoutItem Label="Nation" Margin="10">
<ComboBox x:Name="ComboBox_TargetNazione" />
</dxlc:LayoutItem>
</dxlc:LayoutGroup>
</dxlc:LayoutItem>
</dxlc:LayoutGroup>
</DataTemplate>
C#:
private void Button_Click_Add(object sender, RoutedEventArgs e)
{
DataTemplate tabItemDataTemplate = this.TryFindResource("tabItemContent") as DataTemplate;
DXTabItem tabItem = new DXTabItem();
tabItem.Header = "New Tab";
tabItem.ContentTemplate = tabItemDataTemplate;
tabControl_Targets.Items.Add(tabItem);
}
Here's where to load the list into the combobox:
private void LoadComboBoxNation()
{
ComboBox_TargetNazione.ItemsSource =
ManagementTriple.Istance().get_Nation_byTipologyAndContext(ComboBox_TypologyScenario.SelectedItem.ToString(),
ComboBox_ContextScenario.SelectedItem.ToString());
controlloselecteditem(ComboBox_SourceNazione.SelectedItem.ToString());
controlloselecteditem(ComboBox_TargetNazione.SelectedItem.ToString());
}
Thank you all for the help that you can give me.
DataTemplates require a simple but fundamental requirement to work properly: you should use the ViewModel-First approach.
Ideally, your tab control should have a Binding to some ViewModel. Then, if you want another tab to appear, you should use your button click to call a Command in your ViewModel, then the ViewModel would add another item to your TabControl ItemsSource property (which would be some collection), and the new item would be displayed "automagically" with its respective DataTemplate.
The idea of WPF is to replace all this imperative code in the View (like the one you posted) with a more indirect one, where your only worry is to manipulate things in the ViewModel, and the "Dumb View" just follows.
Hope this helps, but don't hesitate to ask for additional details in the comments.
I'm adding a close button to my tabs using the following guide:
http://www.codeproject.com/Articles/84213/How-to-add-a-Close-button-to-a-WPF-TabItem
This has become a problem because the event uses the 'parent' of the added tab to remove that tab from the tabcontrol. I'm binding the tab control using mvvm, so the parent property is apparently not being set and giving me a null reference exception for the parent when the event tries to remove from it.
Here's the binding so you get the idea:
<TabControl Name="tabControl" Margin="0,22,0.2,-5.2" ItemsSource="{Binding Tabs}" Background="#FF4C76B2"/>
Heres where the tabs are being added.
private void AddTab(object tabName)
{
ClosableTab newTab = new ClosableTab();
newTab.Title = "title?";
//newTab.Header = tabName;
TextBox test = new TextBox();
test.Text = "CONTENT (" + tabName + ") GOES HERE";
newTab.Content = test;
Tabs.Add(newTab);
OnPropertyChanged("Tabs");
}
Here is the event where the null reference is taking place:
void button_close_Click(object sender, RoutedEventArgs e)
{
((TabControl)this.Parent).Items.Remove(this);
}
As I see it there are two options:
try to find another way to remove the tab (without the parent
property)
try to find a way to somehow set the parent property (which cant be
done directly, it throws a compiler error)
That doesn't sound like MVVM to me. We work with data, not UI elements. We work with collections of classes that contain all of the properties required to fulfil some requirement and data bind those properties to the UI controls in DataTemplates. In this way, we add UI controls by adding data items into these collections and let the wonderful WPF templating system take care of the UI.
For example, you have a TabControl that we want to add or remove TabItems from... in a proper MVVM way. First, we need a collection of items that can represent each TabItem:
public static DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<string>), typeof(TestView));
public ObservableCollection<string> Items
{
get { return (ObservableCollection<string>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
I'm just using a DependencyProperty because I knocked this up in a UserControl and I'm just using a collection of strings for simplicity. You'll need to create a class that contains all of the data required for the whole TabItem content. Next, let's see the TabControl:
<TabControl ItemsSource="{Binding Items}" ItemTemplate="{StaticResource ItemTemplate}" />
We data bind the collection to the TabControl.ItemsSource property and we set the TabControl.ItemTemplate to a Resource named ItemTemplate. Let's see that now:
xmlns:System="clr-namespace:System;assembly=mscorlib"
...
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type System:String}">
<TabItem Header="{Binding}" />
</DataTemplate>
This DataTemplate defines what each item in our collection will look like. For simplicity's sake, our strings are just data bound to the TabItem.Header property. This means that for each item we add into the collection, we'll now get a new TabItem with its Header property set to the value of the string:
Items.Add("Tab 1");
Items.Add("Tab 2");
Items.Add("Tab 3");
Note that I included the System XML Namespace Prefix for completeness, but you won't need that because your DataType will be your own custom class. You'll need more DataTemplates too. For example, if your custom class had a Header property and a Content property, which was another custom class, let's say called Content, that contained all of the properties for the TabItem.Content property, you could do this:
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type YourPrefix:YourClass}">
<TabItem Header="{Binding Header}" Content="{Binding Content}" />
</DataTemplate>
<DataTemplate DataType="{x:Type YourPrefix:Content}">
<YourPrefix:SomeUserControl DataContext="{Binding}" />
</DataTemplate>
So this would give you TabItems with Headers set and Content that comes from SomeUserControl which you could design. You don't need to use UserControls, you could just add more UI controls to either DataTemplate. But you will need to add more controls somewhere... and more classes and properties, always remembering to correctly implement the essential INotifyPropertyChanged interface.
And finally, to answer your question in the proper MVVM way... to remove a TabItem, you simply remove the item that relates to that TabItem from the collection. Simple... or it would have been if you really had been using MVVM like you claim. It's really worth learning MVVM properly as you'll soon see the benefits. I'll leave you to find your own tutorials as there are many to chose from.
UPDATE >>>
Your event handling is still not so MVVM... you don't need to pass a reference of any view model anywhere. The MVVM way is to use commands in the view model. In particular, you should investigate the RelayCommand. I have my own version, but these commands enable us to perform actions from data bound Buttons and other UI controls using methods or inline delegates in the view model (where action and canExecute in this example are the CommandParameter values):
<Button Content="Close Tab" Command="{Binding CloseTabCommand}"
CommandParameter="{Binding}" />
...
public ICommand CloseTabCommand
{
get { return new ActionCommand(action => Items.Remove(action),
canExecute => canExecute != null && Items.Contains(canExecute)); }
}
So whatever view model has your Tabs collection should have an AddTabCommand and a CloseTabCommand that add and remove items from the Tabs collection. But just to be clear, for this to work properly, your ClosableTab class should be a data class and not a UI control class. Use a DataTemplate to specify it if it is a UI control.
You can find out about the RelayCommand from this article on MSDN.
I have a Silverlight application with a ComboBox that is filled by VideoCaptureDevice's.
cbVideoDevices.ItemsSource = CaptureDeviceConfiguration.GetAvailableVideoCaptureDevices();
I'm trying to add item, "Select a video device" to the first index but I can't get it to work.
XAML Code:
<ComboBox Height="25" HorizontalAlignment="Left" Margin="0,0,0,0" Name="cbVideoDevices" VerticalAlignment="Top" Width="125" ItemsSource="{Binding AudioDevices}" SelectedItem="{Binding SelectedAudioDevice}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding FriendlyName}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Your explicitly setting the ItemsSource in the code behind and the XAML, choose one or the other. Ideally you would take the XAML approach and set the DataContext appropriately.
Once you make that decision you can insert an item within your ComboBox by using the Items property.
ComboBox box = new ComboBox();
box.Items.Insert(0, "My Item");
A better approach would be to leverage the ICollectionView and simply sort the data and let the UI respond accordingly. Your ItemsSource would then be bound to the ICollectionView.
You can easily insert an item at a desired index location in the Items collection of the ComboBox using the following code.
TextBlock t = new TextBlock();
t.Text = "Select a video device"
combo.Items.Insert(0, t);
Setting the selected index will set the ComboBox to show your added item by default:
combo.SelectedIndex = 0;
or
you can do like this..
YourClassObject objSelectItem = new YourClassObject();
objSelectItem.ID = "0";
objSelectItem.Name = "Select Item";
ComboBox1.Items.Insert(0,objSelectItem);
i hope it will helps you...
I've built an interface formed by a ListView and a panel with a few textboxes. In order to make change the context in those textboxes when another ListViewItem is selected, I've captured the SelectionChange event, to change accordingly DataContexts of the textboxes. Something like this:
void customersList_SelectItem(object sender, SelectionChangedEventArgs e)
{
Customer customer = (Customer)customersList.Selected;
if (customer != null)
{
addressField.DataContext = customer;
phoneField.DataContext = customer;
mobileField.DataContext = customer;
webField.DataContext = customer;
emailField.DataContext = customer;
faxField.DataContext = customer;
...
}
}
Now, I wonder, is this the best the way to do it? Looks like a bit forced, but I can't devise any better.
If the textboxes were all contained in a containing element (e.g. a Grid), then you could just set the DataContext of the Grid element instead. This would be cleaner.
Even better yet would be to use XAML binding and MVVM, and this code could be implemented declaratively in XAML.
Bind the dependent controls DataContext property to the ListBox's SelectedItem property.
Or better yet if they are within a container control - Set its data context once and have the children inherit it. Something like...
<StackPanel DataContext="{Binding ElementName=ListBoxName, Path=SelectedItem}">
<!--- dependent controls -->
</StackPanel>
You can also use the "/" binding path syntax in WPF in combination with a CollectionView:
<Window ... xmlns:local="...">
<Window.DataContext>
<local:MyViewModel ... />
</Window.DataContext>
<Window.Resources>
<CollectionViewSource x:Key="ItemsView" Source="{Binding Path=Items}" />
<Window.Resources>
<ListView ItemsSource="{Binding Source={StaticResource ItemsView}}">
...
</ListView>
<Grid DataContext="{Binding Source={StaticResource ItemsView}, Path=/}">
...
</Grid>
</Window>
To quickly explain this setup:
The window's datacontext is set to an instance of a view model
A CollectionViewSource is created as a resource and uses a collection exposed by the view model as its source
The listview's ItemsSource is bound directly to the CollectionView (exposed by CollectionViewSource)
The Grid (which would contain your form elements) is bound to the CurrentItem of the CollectionView via the "/" binding path syntax; each time an item is selected in the list view, the Grid's datacontext is automatically set to the currently selected item
I prefer this type of binding over having to reference specific elemetns and properties and relying on the built in power of WPF's binding and CollectionView classes.