Adding TabItems dynamically - c#

I have a TabControl control
<TabControl Name="Farms_myVillages"
ItemsSource="{Binding Villages}">
</TabControl/>
In the code behind I add some tabs dynamically to the TabControl as follows:
foreach (Village vill in Villages)
{
TabItem tab = new TabItem();
tab.Header = vill.Name;
VillageUserControl c = new VillageUserControl();
c.DataContext = vill;
tab.Content = c;
Farms_myVillages.Items.Add(tab);
}
where VillageUserControl is a UserControl that deal with the specified village. This code works fine and it gets the expected results...
The problem is that I don't want this to be in the code behind but just in the xaml itself.
I try this:
<TabControl Name="Farms_myVillages"
ItemsSource="{Binding Villages}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="Content">
<Setter.Value>
<u:VillageUserControl DataContext="{Binding}"/>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
After I run it, it throws an exception: "Specified element is already the logical child of another element. Disconnect it first."
Did I miss something? Please help me here...

You set the wrong thing, you should not modify the ItemContainerStyle but the TabControl.ItemTemplate for the header and TabControl.ContentTemplate for the content.
(The exception may have to do with the fact that in the style only one VillageUserControl is created, but the style applies to multiple tab items.)

Now it is working:
<TabControl Name="Farms_myVillages"
ItemsSource="{Binding Villages}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<u:VillageResources/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>

Your approach of not having this in code behind is right, instead of using ItemContainerStyle use ItemTemplate and ContentTemplate. You can have a look at this sample from Josh Smith for creating a tabs using Templates and Styles -
http://code.msdn.microsoft.com/mag200902MVVM/Release/ProjectReleases.aspx?ReleaseId=2026

Related

MenuCollection Binding twice MVVM + Databinding Child menu items

As you can see in the image below, you can see 2 hover states. Here is the XAML
<Menu ItemsSource="{Binding Data.MenuCollection}">
<Menu.ItemTemplate >
<DataTemplate DataType="MenuItem">
<MenuItem Header="{Binding Header}" Command="{Binding Command}" ItemsSource="{Binding Children}"/>
</DataTemplate>
</Menu.ItemTemplate>
</Menu>
The Collection of data works on the header. However I can't get the Children nodes to appear.
public void CreateTempMenuList()
{
MenuCollection = new ObservableCollection<MenuItem>()
{
new MenuItem()
{
Header = "File",
Children = new ObservableCollection<MenuItem>()
{
new MenuItem()
{
Header = "Exit"
}
}
}
};
}
The MenuItem class is something I created. Each property has a setter that called the OnPropertiesChanged Function. I can add the class if needed, but I am pretty sure thats not the problem.
So my question is. How do i get rid of the 'double' hover. In the image you can see 2 borders. An outer border which i hover over. the hover stays until focused on something else.
My second question is how can i get the child items to work? The itemssource on the menuitem tag could be wrong but its all i could think of.
Define an HierarchicalDataTemplate:
<Menu ItemsSource="{Binding Data.MenuCollection}">
<Menu.Resources>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</Menu.Resources>
<Menu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Header}" />
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
</Menu>
A System.Windows.Controls.MenuItem container is implicitly created for each item so you shouldn't add another MenuItem element in the template.
Also make sure that you don't bind to an ObservableCollection<System.Windows.Controls.MenuItem> because the ItemTemplate won't be applied to built-in MenuItem elements.
To make your current code work, right click your Menu control > Edit Additional Template > Edit ItemContainerStyle > Edit Copy.
And in the generated Style,
Search for this piece of code :
<Trigger Property="Role" Value="TopLevelItem">
<Setter Property="Padding" Value="7,2,8,3"/>
<Setter Property="Template" Value="{DynamicResource {ComponentResourceKey ResourceId=TopLevelItemTemplateKey, TypeInTargetAssembly={x:Type MenuItem}}}"/>
</Trigger>
And change Padding to 0 instead of 7,2,8,3 .

No Style Inheritance Inside ItemsControl / DataTemplate in WPF?

can anyone explain why the TextBlock inside my DataTemplate does not apply the style defined in my UserControl.Resources element, but the second TextBlock ('Test B') does?
I think it may have to do with a dependency property somewhere set to not inherit, but I can't be sure.
<UserControl.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Padding" Value="8 2" />
</Style>
</UserControl.Resources>
<StackPanel>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!--Padding does not apply-->
<TextBlock>Test A</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!--Padding applies-->
<TextBlock>Test B</TextBlock>
</StackPanel>
Templates are considered as a boundary. Elements within the templates falls in this boundary range, and look up for the style with a matching target type ends within this range at runtime as a result the TextBlock outside will pickup the style and the one inside wont. like adminSoftDK said you should give the style an x:Key and then apply it as static resource it will work.

Access a control from within a DataTemplate with its identifying name

In my WPF application I have a ComboBox control that is located inside a Grid Control. In XAML I am assigning a name to the ComboBox:
<DataGridTemplateColumn Header="Status">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock VerticalAlignment="Center" Text="{Binding name_ru}" Width="Auto" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox Name="stcom" Style="{DynamicResource ComboBoxStyle}" SelectionChanged="status_SelectionChanged" Height="auto" Width="Auto">
<ComboBox.BorderBrush>
<SolidColorBrush Color="{DynamicResource Color1}"/>
</ComboBox.BorderBrush>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
With the method FindName(string) I am trying to refer to the ComboBox with its associated name:
ComboBox stcom
{
get
{
return (ComboBox)FindName("stcom");
}
}
if (stcom != null)
{
stcom.ItemsSource = list;
}
But obviously the control can not be found because the reference stcom remains null.
The question now is how to refer to my ComboBox using its name property ?
The answer is:
<Style x:Key="CheckBoxStyle1" TargetType="{x:Type CheckBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<StackPanel Orientation="Horizontal">
<Grid>
<TextBlock Name="tbUserIcon" Text="t1" />
<TextBlock Name="tbCheck" Text="✓" />
</Grid>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
and C#:
checkBox.ApplyTemplate();
var tbUserIcon= (TextBlock)checkBox.Template.FindName("tbUserIcon", checkBox);
don't forget the checkBox.ApplyTemplate() be fore Template.FindName() it's important!
First you have to get access to the control template which it has been applied to, then you can find an element of the template by name.
Have a look at the MSDN knowledge base :
How to: Find ControlTemplate-Generated Elements
You can't access controls that are part of a DataTemplate with their name.
You can try to read about some workarounds for example
WPF - Find a Control from DataTemplate in WPF
You can also have a look at the dozens of posts here on SO issuing this topic for example
here
here
here
here
here
here
here
here

C#/XAML/WPF binding working partially, only displays first item in List

I have what should be a really simple binding, but the problem I'm seeing is that instead of displaying the three companies (company_list is a List, where Company contains a company_id to bind to), I see the window pop up with only the first company_id in company_list. I have other bindings which seem to work fine, and in some other cases I see that I've used ItemSource instead of DataContext, but when I use that I get "Items collection must be empty before using ItemsSource". I've searched extensively for a simple answer to this in stackoverflow, msdn and elsewhere, and have seen mostly really complex solutions that I haven't been able to understand/apply.
When my window appears, it has:
CompanyA
where it should have:
CompanyA
CompanyB
CompanyC
which is the content of the company_list (yes, verified in debugger). Suggestions appreciated! Code and XAML follow.
ReadMasterCompanyList(); // populates a_state.company_list with 3 companies
// display company list dialog
CompanySelect cs_window = new CompanySelect();
cs_window.CompanyListView.DataContext = a_state.company_list;
// fails: cs_window.CompanyListView.ItemsSource = a_state.company_list;
cs_window.Show();
And the XAML from CompanySelect:
<Grid>
<ListView IsSynchronizedWithCurrentItem="True"
x:Name="CompanyListView"
SelectionMode="Single" SelectionChanged="CompanyListView_SelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Height" Value="30"/>
</Style>
</ListView.ItemContainerStyle>
<ListViewItem Content="{Binding Path=company_id}"></ListViewItem>
</ListView>
</Grid>
I would set the ItemsSource of the ListView, rather than the DataContext, either in codebehind:
cs_window.CompanyListView.ItemsSource = a_state.company_list;
or with binding:
<ListView ItemsSource="{Binding company_list}">
And then set the ItemTemplate of the ListView instead.
...
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding company_id}" />
</DataTemplate>
</ListView.ItemTemplate>
...
I would also look into using the MVVM design pattern for testability and separation of concerns, and look at using PascalCase for your property names.
Also, unless you specifically wanted a ListView, I would use a ListBox.
First, set the DataContext only after cs_window.Show().
Second, the ListViewItem you have as a child in your ListView's XAML is why you're only seeing one.
Third, might work better (and would be more MVVM-ish) if you define ItemsSource in the XAML, like this:
<ListView ItemsSource="{Binding Path=company_list}" ...>
That's after making a_state the DataContext of the ListView's container or some other ancestor element.
The problem is, that you define one ListViewItem in your XAML code. You shouldn't do this.
Try something like this:
<Grid>
<ListView IsSynchronizedWithCurrentItem="True"
x:Name="CompanyListView"
SelectionMode="Single" SelectionChanged="CompanyListView_SelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Height" Value="30"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Content={Binding Path=company_id}/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>

Error: The name 'tBox' does not exist in the current context

Error: The name 'tBox' does not exist in the current context.
XAML:
<ItemsControl Name="itemsControl">
<ItemsControl.Template>
<ControlTemplate>
<WrapPenel>
<ItemsPresenter/>
</WrapPenel>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Name="tBox" Text="{Binding Name}"></TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
C#:
tBox.Background=Brushes.White; // Error: The name 'tBox' does not exist in the current context.
How to access control?
The TextBlock you named tBox is inside a DataTemplate. Controls inside a template are in a different name scope, so you can't access it in code-behind via its name. I'm not sure but you might get it via the ItemTemplate property and casting it to a TextBlock. Or you can add a property in your code-behind representing the background and use binding on the TextBlock's Background property. Hope this helps.
Set it on the TextBlock, in your DataTemplate:
<DataTemplate>
<TextBlock Name="tBox" Background="White" Text="{Binding Name}"></TextBlock>
</DataTemplate>
Or if you wish to only set the Background in certain conditions, consider using Triggers:
<DataTemplate>
<TextBlock Name="tBox" Text="{Binding Name}"></TextBlock>
<DataTemplate.Triggers>
<Trigger SourceName="tBox" Property="IsMouseOver" Value="True">
<Setter TargetName="tBox" Property="Background" Value="White" />
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
More information on how to use Triggers can be found here: A Guided Tour of WPF - Part 4 (Data Templates and Triggers)
I didn't try but maybe the answer here works:
Access a control from within a DataTemplate with its identifying name
to use something like :
var tbUserIcon= (TextBlock)checkBox.Template.FindName("tbUserIcon", checkBox);
But I think this way isn't convenient at all, especially if there's lots of controls have to do it this way, and it can't be checked by intellisense when writing code real time.
this.Background=Brushes.White; (assuming its code behind the control)?
Since Background is a dependency property, you will have to use
tBox.SetValue(BackgroundProperty, new SolidBrush(Color.White));

Categories