I'm new in WPF and try learn VMMV. I try to create TabControl with template for content in xaml.
I want tabitem with content of grid and in grid list of usercontrols. After adding a usercontrol the header of tabitem render right but nothing is in content. What is wrong?
This is my xaml:
<TabControl ItemsSource="{Binding Items}" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Visibility="Hidden" Name="tcContent" Grid.Column="1" Grid.Row="0">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Viewmodel:
public class Tab
{
public string Header { get; set; }
public ObservableCollection<UserControl> Content { get; set; }
}
public class MainWindowsViewModel
{
ObservableCollection<Tab> _items = new ObservableCollection<Tab>();
public ObservableCollection<Tab> Items
{
get
{
return _items;
}
}
}
Behind code for fill tabcontrol:
public MainWindow()
{
this.DataContext = new MainWindowsViewModel();
}
public void AddToTab(string header, UserControl c)
{
Tab tab = new Tab();
tab.Header = header;
tab.Content = new ObservableCollection<UserControl>();
tab.Content.Add(c);
((MainWindowsViewModel)this.DataContext).Items.Add(tab);
}
You are misunderstanding the MVVM principle.
In your view-model, you have a collection of the Tab objects, and each of them holds a collection of UserControls. In this way, your view-model contains some view elements (UserControls). In MVVM, you shouldn't do that.
Instead, you create the view-models for each tab item that describe a model of the representation (hence view-model); and in XAML, you describe how do these view-models should look like using DataTemplates.
But this is all required only if your views have to be dynamic. E.g. you don't know which data will be available because you're fetching them from a database.
If your TabItems display a set of UserControls that won't change, then just describe your view completely in XAML, without any DataTemplates.
Firstly, remove Visibility="Hidden" form the TabControl. Then change the ControlControl to ItemsControl which can hold Tab.Content, which is a collection. However, you should pay attention to the problems mentioned in #dymanoid's answer.
<TabControl.ContentTemplate>
<DataTemplate >
<ItemsControl ItemsSource="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
Related
I am trying to create horizontal tabs that load vertical tabs dynamically using WPF something like below. I want to create something similar, this is not a screenshot of my application.
It seems like I cannot create a tab control inside a tab item.
My XAML file has the following code where I am trying to add a TabControl inside the content of the tab:
<Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2">
<TabControl ItemsSource="{Binding Tabs}">
<TabControl.ItemTemplate>
<!-- this is the header template-->
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<!-- this is the body of the TabItem template-->
<DataTemplate>
<TabControl ItemsSource="{Binding steps}">
<TabControl.ItemTemplate>
<!-- this is the header template-->
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<!-- this is the body of the TabItem template-->
<DataTemplate>
<TextBlock Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
<TextBlock Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
My C# code includes the following objects:
public ObservableCollection<TabItem> Tabs { get; set; }
public AddElement()
{
Steps step = new Steps();
step.display = "1";
Steps step2 = new Steps();
step.display = "2";
ObservableCollection<Steps> stepsList = new ObservableCollection<Steps>();
stepsList.Add(step);
stepsList.Add(step2);
Tabs = new ObservableCollection<TabItem>();
Tabs.Add(new TabItem { Header = "One", Content = "One's content", Steps = stepsList });
Tabs.Add(new TabItem { Header = "Two", Content = "Two's content" });
}
public sealed class TabItem
{
public string Header { get; set; }
public string Content { get; set; }
public ObservableCollection<Steps> Steps { get; set; }
}
public class Steps
{
public string display { get; set; }
}
There are multiple issues in your code, that you need to fix to make it work as intended.
There is a typo in the binding of the Steps property. It has to start with a capital letter.
<TabControl ItemsSource="{Binding Steps}">
Remove the last from the XAML or put it in a Grid like below. As it is, the code will not compile, as you can only set a single element as content in a DataTemplate.
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TabControl Grid.Row="0" ItemsSource="{Binding steps}">
<!-- ...other code. -->
</TabControl>
<TextBlock Grid.Row="1" Text="{Binding Content}" />
</Grid>
</DataTemplate>
You bind to Header and Content properties on Steps items in XAML. This type does not have any of these properties, only display. You could introduce these properties and remove display.
public class Steps
{
public string Header { get; set; }
public string Content { get; set; }
}
In AddElement you only modify the step instance, not step2, so it will be empty. Initialize both, e.g. with the abovementioned additional properties.
Steps step = new Steps();
step.Header = "Header 1";
step.Content = "Content 1";
Steps step2 = new Steps();
step2.Header = "Header 2";
step2.Content = "Content 2";
If you want the tab strip of your inner TabControl to be displayed vertically on the left, you have to set the TabStripPlacement property accordingly.
<TabControl ItemsSource="{Binding Steps}" TabStripPlacement="Left">
The result will look like this with default WPF styles.
I have a list view control that uses the DataTemplate to bind the the data.
After I created the dependency the binding works with a simple string but will not work when I bind the class property. If I bind the data to a textBlock it works, but if I bind the same thing to my User Control it doesn't work.
Here is my XAML: LISTVIEW
<ListView x:Name='lbUsers'
Height='370'
Canvas.Left='264'
Canvas.Top='183'
Width='1177'
Background='{x:Null}'>
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<Views:UserSelectRibbon NameText ="{Binding Name}" />
<Image Width='10' />
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Width="{Binding (FrameworkElement.ActualWidth),
RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}"
ItemWidth="{Binding (ListView.View).ItemWidth,
RelativeSource={RelativeSource AncestorType=ListView}}"
MinWidth="{Binding ItemWidth, RelativeSource={RelativeSource Self}}"
ItemHeight="{Binding (ListView.View).ItemHeight,
RelativeSource={RelativeSource AncestorType=ListView}}" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
HERE IS MY USER CONTROL:
public string NameText
{
get { return (string)GetValue(NameTextProperty); }
set { SetValue(NameTextProperty, value); }
}
public static readonly DependencyProperty NameTextProperty =
DependencyProperty.Register("NameText", typeof(string), typeof(UserSelectRibbon),null);
public UserSelectRibbon()
{
InitializeComponent();
DataContext = this;
}
HERE IS MY SIMPLE USER CLASS :
public class User {
public string Name { get; set; }
public int Age { get; set; }
public int Playlist { get; set; }
}
SO:
In the XAML if I do this : WORKS
<Views:UserSelectRibbon NameText ="Some text here" />
This will work and add the text to the TextBlock in the user control
BUT:
In the XAML if I do this : DOESN'T"T WORK
<Views:UserSelectRibbon NameText ="{Binding Name}" />
I would like to know why it works without the binding but it doesn't work with the binding
The problem is with your DataContext:
public UserSelectRibbon()
{
InitializeComponent();
DataContext = this;
}
This has an implication on the following:
<Views:UserSelectRibbon NameText ="{Binding Name}" />
In here, the DataContext is already changed to the Views:UserSelectRibbon, so you can't bind to anything from the outer DataContext anymore.
Solution: do not set the DataContext of a UserControl to itself from the inside. Never!
Instead, set it on an element inside the usercontrols tree or use some RelativeSource or ElementName binding.
Solution with inner DataContext:
<UserControl ...>
<Grid x:Name="grid1">
<TextBlock Text="{Binding NameText}"/>
</Grid>
</UserControl>
and
public UserSelectRibbon()
{
InitializeComponent();
grid1.DataContext = this; // set DataContext on inner control instead of the usercontrol itself
}
Solution with RelativeSource (you can use UserControl base type or your specific usercontrols inner type):
<TextBlock Text="{Binding NameText,RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"/>
Solution with ElementName:
<UserControl ...
x:Name="myControl">
<Grid>
<TextBlock Text="{Binding NameText,ElementName=myControl}"/>
</Grid>
</UserControl>
I did something like this recently with an itemsControl. I'm probably gonna forget everything though.
Anyway, there's a simpler way then binding straight from the item itself. Instead, you call the item from within your c# and have the binding there. After
Mainpage()
{
InitializeComponent();
}
Still in the brackets, you should add this:
List<SampleItem> = new List<SampleItem>;
items.Add({NameText = Name});
lbusers.ItemsSource = items;
And outside the brackets:
public class SampleItem{
public string Name {get; set;}
}
You can add the items.Add line as many times as you want, and that's how many items will show up.
My XAML is as under. I have a main ViewModel which has a list of items and I want to display a property within this list
<ItemsControl ItemsSource="{Binding MyList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding MyName, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"></Label>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The problem is that MyName is always blank although my list has two items.
The main VM class has this property below and I add items in the constructor
public ObservableCollection<InnerViewModel> MyList { get; set; }
My inner VM has
public class InnerViewModel
{
private string _MyName;
public string MyName
{
get
{
return _MyName;
}
set
{
_MyName = value;
OnPropertyChanged("MyName");
}
}
I do have OnPropertyChanged in place but I'm not pasting it here for simplicity. I think the problem is with the XAML but I'm not sure. How do I get the property MyName to be displayed in my list of items in the view?
Since you use MyList as the ItemsSource, the data source for the child elements will be MyList. So you do not need to use the RelativeSource.
In other words, this should work :
<ItemsControl ItemsSource="{Binding MyList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding MyName}"></Label>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Try and remove the relative source part of the binding.
<DataTemplate>
<Label Content="{Binding MyName}"></Label>
</DataTemplate>
I'm writing simple WPF Application and I wanted to use ListView to display List of items. My code is:
WPF.xaml
<ListView Grid.Column="0" Grid.Row="1" Margin="10,0,10,5" ItemsSource="{Binding MyCollection.Elements}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ElementDescriptions}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
WPF.xaml.cs
public MyViewModel ViewModel
{
get { return DataContext; }
set { DataContext = value; }
}
MyViewModel.cs
public OwnedCollection Elements { get; set; }
OwnedCollection.cs
public List<ElementDescriptions> ElementDescriptions { get; set; }
I'm 100% sure, that communication between View and ViewModel is correct, because displaying simple message doesn't make me troubles. Am I doing right binding in ListView?
A couple things:
First,
TextBlock Text="{Binding ElementDescriptions}"
doesn't make a lot of sense because ElementDescriptions is a collection. If you want to loop through all the ElementDescriptions in your List you should really be binding the ItemSource of the ListView to ElementDescriptions then accessing some text property of the ElementDescriptions class:
<ListView Grid.Column="0" Grid.Row="1" Margin="10,0,10,5" ItemsSource="{Binding MyCollection.ElementsElementDescriptions }">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ElementDescriptions.SomeTextField}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Second, are you using INotifyPropertyChanged so the view knows to update? More info on that here: OnPropertyChanged with a List
I have a listbox inside a listbox, and both are binded to an observable collection. I have overloaded the SelectionChanged event for both. For the nested listbox, I have a few labels in its data template. I want to be able to get the content of those labels. It's just difficult because I cannot refer to any of them in the code behind, even with the x:name property defined. Anyone have any ideas?
<ListBox Grid.Row="5" x:Name="lb1" ItemsSource="{Binding}" DataContext="{Binding}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" SelectionChanged="lb1_SelectionChanged">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Label x:Name="txtEnclosure" Content="{Binding Path=EnclosureID}"/>
<......other labels bound to other properties...>
<ListBox x:Name="lbserver" ItemsSource="{Binding Path=Slist}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Label x:Name="txtSlot" Content="{Binding Path=Slot}" />
<Label x:Name="txtServer" Content="{Binding Path=HostnameID}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The parent listbox binds to an observable collection called Elist (an observable collection of Enclosures, a class I defined)
this.DataContext = Settings.Elist;
And the child listbox binds to an observable collection inside of the Enclosure class.
public class Enclosure
{
public ObservableCollection<Server> Slist { get; set; }
...contains other variables as well....
}
In the application, it lists enclosures, and each enclosure has a list of servers. The user can select an Enclosure, and I can get the Enclosure from Elist based on the SelectedIndex (I use ElementAt(SelectedIndex)). Things just get much more tricky when I try to get one of the Servers from the nested listbox. I want to be able to select one of the servers in the list and get the Server from the observable collection Slist. The problem is that when the user selects the server directly, I don't know which Enclosure from Elist the server is from, aaand I can't get the SelectedIndex because I can't refer to anything from the nested listbox in the code behind >.< A very frustrating problem indeed...does anyone have any ideas?
If I can get at the items in the nested listbox in code that would be helpful as well.
Below sample shows how to get selected parent and child when user selects a child, see OnSelectedModelChanged method.
XAML:
<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">
<StackPanel>
<ListBox ItemsSource="{Binding .}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold"/>
<ListBox ItemsSource="{Binding Path=Models}" SelectionChanged="OnSelectedModelChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
Code behind:
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
using System.Collections.Generic;
using System.Windows.Controls.Primitives;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<Make> cars = new List<Make>();
cars.Add(new Make("Ford") { Models = new List<Model>() { new Model("F150"), new Model("Taurus"), new Model("Explorer") } });
cars.Add(new Make("Honda") { Models = new List<Model>() { new Model("Accord"), new Model("Pilot"), new Model("Element") } });
DataContext = cars;
}
private void OnSelectedModelChanged(object sender, SelectionChangedEventArgs e)
{
Selector modelSelector = sender as Selector;
Model selectedModel = modelSelector.SelectedItem as Model;
Make selectedMake = modelSelector.DataContext as Make;
}
}
public class Make
{
public Make(string name)
{
Name = name;
}
public string Name { get; private set; }
public IEnumerable<Model> Models { get; set; }
}
public class Model
{
public Model(string name)
{
Name = name;
}
public string Name { get; private set; }
}
}