I have ControlTemplate (XAML).
I need in code behid add ControlTemplate to TabItem.Content.
var tabItem = new TabItem
{
DataContext = listDesk,
Header = headerText,
Content = ???
};
XAML
<ControlTemplate x:Key="MyTabItemContentTemplate" TargetType="controls:TabItem">
<StackPanel>
<TextBlock Text="wwwwww"/>
</StackPanel>
</ControlTemplate>
I use SL4
I think you're confusing concepts. Instead of setting the Content, which is data, set the Template, which is the visual representation of the control:
var tabItem = new TabItem
{
DataContext = listDesk,
Header = headerText,
Template = this.FindResource("MyTabitemContentTemplate") as ControlTemplate
};
Moreover, chances are there is no reason to do this in code. You could be doing it entirely in XAML.
Related
I tried to create a TabItem with a TextBox in it from this article.
I found out that the TabItem Header is NOT bind to the TextBox.
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
TabItem ti = new TabItem();
DataTemplate tabItemTemplate = new DataTemplate();
tabItemTemplate.DataType = typeof(TabItem);
FrameworkElementFactory textBoxFactory = new FrameworkElementFactory(typeof(TextBox));
textBoxFactory.SetBinding(TextBox.TextProperty, new Binding("."));
textBoxFactory.SetValue(NameProperty, "textBox");
textBoxFactory.SetValue(BorderThicknessProperty, new Thickness(0));
//textBoxFactory.SetValue(IsEnabledProperty, false);
tabItemTemplate.VisualTree = textBoxFactory;
ti.Header = "Test!";
ti.HeaderTemplate = tabItemTemplate;
ti.MouseDoubleClick += TabItem_MouseDoubleClick;
tabControl.Items.Add(ti);
}
private void TabItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
MessageBox.Show((sender as TabItem).Header.ToString());
}
Could somebody please be so kind and teach me how to bind it correctly?
Much appreciated!
Your Binding configuration is wrong.
new Binding(".") is missing the Binding.Source.
It should be:
var binding = new Binding("Header") { Source = ti };
Example of using XAML
The following example replicates your C# code
<TabControl>
<TabItem Header="Test" MouseDoubleClick="TabItem_MouseDoubleClick" >
<TabItem.HeaderTemplate>
<DataTemplate>
<TextBox x:Name="textBox"
Text="{Binding RelativeSource={RelativeSource AncestorType=TabItem}, Path=Header}" />
</DataTemplate>
</TabItem.HeaderTemplate>
</TabItem>
</TabControl>
Complementing #BionicCode's answer
The problem with your original binding is that you are pointing to the current Data Context.
Your binding is equivalent to an empty Binding "new Binding ();".
Thus, you get a binding not to a property, but to a source (The default source is the current Data Context).
But the binding can only change the property of the source, not the source itself.
The Header template is applied to the contents of the TabItem's Header property.
Therefore, to get the same value, but not as a source of the binding, but as a property in the Path of the binding, you need to go up to the TabItem level.
#BionicCode in his answer showed you examples of such bindings.
I will offer another option for integration into your code:
private static readonly DataTemplate headerTemplate = (DataTemplate)XamlReader.Parse
(#"
<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<TextBox x:Name='textBox'
Text='{Binding Header,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabItem}},
UpdateSourceTrigger=PropertyChanged}'
BorderThickness='0'/>
</DataTemplate>
");
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
TabItem ti = new TabItem();
ti.Header = "Test!";
ti.HeaderTemplate = headerTemplate;
ti.MouseDoubleClick += TabItem_MouseDoubleClick;
tabControl.Items.Add(ti);
}
I have a TabControl with some tabs declared in XAML. I want to add new tabs and bind their IsEnabled properties to some properties of their content:
for (int i = 0; i < context.Pictures.Count; ++i)
{
var tabItem = new TabItem();
var title = "Some title"
tabItem.Header = title;
var image = new Image();
Binding sourceBinding = new Binding(nameof(context.Pictures) + $"[{i}]");
sourceBinding.Source = context;
image.SetBinding(Image.SourceProperty, sourceBinding);
image.Width = 800;
image.Height = 600;
DataTrigger isEnabledTrigger = new DataTrigger() { Binding = sourceBinding, Value = null };
isEnabledTrigger.Setters.Add(new Setter(TabItem.IsEnabledProperty, false));
tabItem.Content = image;
tabControl.Items.Add(tabItem);
}
I want to disable tab if the picture inside is null (apply isEnabledTrigger). Problem here is that style of tabItem is derived from tabControl containing it, so I cannot just create a style with my trigger and apply it to TabItem. Sure, I could just copy original style and hardcode it, but I don't think it's a good way to solve my problem.
So, to solve my problem I have two ideas:
Create a shallow copy of existing style, add trigger and apply it
Load original style from XAML, add trigger and apply it (may be difficult, since it lies in another project)
Is there more rational way to bind TabControls IsEnabled to contained Images value?
Don't add TabItem directly. Use data models. This is recommended approach for all item controls. Then define a DataTemplate for the data model(s) and assign it to TabControl.ContentTemplate.
Use the TabControl.ItemTemplate to layout the header.
Defining a Style for the TabControl.ItemContainerStyle allows you to set up the required triggers quite easily. Doing layout using C# is never a good idea. Always use XAML.
See: Data binding overview in WPF, Data Templating Overview
The minimal model class should look like this:
PictureModel.cs
// All binding source models must implement INotifyPropertyChanged
public class PictureModel : INotifyPropertyChanged
{
private string title;
public string Title
{
get => this.title;
set
{
this.title = value;
OnPropertyChanged();
}
}
private string source;
public string Source
{
get => this.source;
set
{
this.source = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ViewModel.cs
class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<PictureModel> Pictures { get; }
private void CreateTabItems(Context context)
{
foreach (string imageSource in context.Pictures)
{
var pictureModel = new PictureModel()
{
Title = "Some Title",
Source = imageSource
};
this.Pictures.Add(pictureModel);
}
}
}
MainWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<!-- Layout the tab content -->
<TabControl ItemsSource="{Binding Pictures}">
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type viewModels:PictureModel}">
<Image Source="{Binding Source}" />
</DataTemplate>
</TabControl.ContentTemplate>
<!-- Layout the tab header -->
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewModels:PictureModel}">
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</TabControl.ItemTemplate>
<!-- Setup triggers. The DataContext is the current data model -->
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Source}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
</Window>
You should base your Style on the current Style:
Style style = new Style(typeof(TabItem))
{
BasedOn = FindResource(typeof(TabItem)) as Style
};
DataTrigger isEnabledTrigger = new DataTrigger() { Binding = sourceBinding, Value = null };
isEnabledTrigger.Setters.Add(new Setter(TabItem.IsEnabledProperty, false));
style.Triggers.Add(isEnabledTrigger);
tabItem.Style = style;
Or
Style style = new Style(typeof(TabItem))
{
BasedOn = tabControl.ItemContainerStyle
};
...
...depending on how your current Style is applied.
This is how you would extend an existing Style with your DataTrigger and this is a good way of solving this.
I have to bind dynamic the Text property for the TextBlock in the WPF application in the runtime.
Here is the code:
In xaml file
<DataTemplate x:Key="Double_View_Template">
<TextBlock
x:Name="txtDoubleViewTemplate"
HorizontalAlignment="Left"
VerticalAlignment="Center"
/>
</DataTemplate>
In C#
DataTemplate data = FindResource("Double_View_Template") as DataTemplate;
TextBlock ui = data.LoadContent() as TextBlock;
Binding binding = new Binding();
binding.Path = new PropertyPath("Mass");
BindingOperations.SetBinding(ui, TextBlock.TextProperty, binding);
DataGridTemplateColumn column = new DataGridTemplateColumn();
column.CellTemplate = data;
instrumentDataGrid.Columns.Add(column);
When run the application I see only blank lines, and the values are not shown in the Datagrid. The ItemsSource and the DataContext is correctly set.
If I set
Text="{Binding Path=Mass}"
in the xaml the data is displayed.
Any idea why in the runtime the binding is not set?
I am creating TabItems programmatically without any issue by using the following code:
var tabItem = new TabItem();
tabItem.Header = "My Tab Header";
tabItem.Content = new UserControl1();
MainTabControl.Items.Add(tabItem);
Now when a tab item added to tab control i also want to add image button at the same time with the creation of TabItem aligned at right side. How can i achieve this? thanks in advance.
EDIT:
I have tried a lot and still did not get an idea. following is my tabcontrol in xaml and ObservableCollection. When i run project tabs shows successfully but i don't know how to add images in it because in my tab control in xaml, it does not have TabItems markup and they are displaying automatically when running project. Please view my sample code and transform into my desired result. I wana conclude and close this issue, I Really thanks and appreciate help.
Following is xaml:
<TabControl ItemsSource="{Binding TabItems, Mode=TwoWay}" DisplayMemberPath="Content" HorizontalAlignment="Left" Height="73" Margin="10,25,0,0" VerticalAlignment="Top" Width="312"/>
Following is viewmodel
namespace WpfApplication1.ViewModels
{
public class VMTabControl : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyname)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyname));
}
[![enter image description here][1]][1]}
public VMTabControl()
{
TabItems = new ObservableCollection<clsTabs>(GetList().ToList());
}
private ObservableCollection<clsTabs> _tabItems;
public ObservableCollection<clsTabs> TabItems
{
get { return _tabItems; }
set
{
_tabItems = value;
OnPropertyChanged("TabItems");
}
}
public List<clsTabs> GetList()
{
List<clsTabs> tablist = new List<clsTabs>();
tablist.Add(new clsTabs { Content = "First", ImgPath = "path" });
tablist.Add(new clsTabs { Content = "Second", ImgPath = "path" });
return tablist;
}
}
}
In code
TabItem.Header is not limited to displaying strings - you can set any UI control on it. For example:
tabItem.Header = new Button { Content = "Click me" };
To display both text and a close button, you could use a horizontal stack panel that contains a text block and a button.
In XAML
However, UI layouts are most often written in XAML. The following XAML assumes you have an Items property in your view model, which is a collection of items. These items have a TabHeaderName and TabImagePath property. The view model should also have a RemoveTabCommand property, which is an ICommand that takes a single argument (the tab item to be removed):
<TabControl ItemsSource="{Binding Items}">
<!-- // If you only need to display a single property, you can use DisplayMemberPath.
// If you need something more fancy (such as a text-block and button next to each other),
// you'll have to provide a tab header template instead: -->
<TabControl.ItemTemplate>
<DataTemplate>
<!-- // Tab item header template (this is how each tab header will look): -->
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding TabHeaderName}" />
<Button Content="X"
Command="{Binding DataContext.RemoveTabCommand, RelativeSource={RelativeSource AncestorType=TabControl}}"
CommandParameter="{Binding}" />
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<!-- // Tab item content template (this is how each tab content will look): -->
<Image Source="{Binding TabImagePath}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
If Items is an observable collection, simply adding an item to it will automatically add a tab item for it. Likewise, removing an item will remove its tab item.
Try this:
var tabItem = new TabItem();
var stack = new StackPanel();
var t = new TextBlock();
t.Text = "My Tab Header";
var i = new Image();
//i.Source = ...
stack.Children.Add(t);
stack.Children.Add(i);
tabItem.Header = stack;
tabItem.Content = new StackPanel();
tab.Items.Add(tabItem);
I am trying to Bind the TextBlock inside ComboBox through Code. I am able to bind the textblock sucessfully but For some reasons TextBlock doesnt display Text Values.
I have mechanism which checks for the selected values and there I am getting the selected values without any problem.
So to conclude, My binding is working fine but I am missing out something hence textblock is not displaying text that is bound with it.
This is the code I am using for Binding:
where "lObjTextBlock" is TextBlock inside of ComboBox.
TextBlock lObjTextBlock = (TextBlock)ComboBox.ItemTemplate.LoadContent();
Binding lObjBinding = new Binding();
lObjBinding.Path = new PropertyPath("[" + lObjMap.PropertyName + "]");
lObjTextBlock.SetBinding(TextBlock.TextProperty, lObjBinding);
This is the XAML for the TextBlock:
<my:HComboBox Name="cmbRefDoctor">
<my:HComboBox.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="txtRefDoctorName" />
</DataTemplate>
</my:HComboBox.ItemTemplate>
</my:HComboBox>
Once again : My problem is that TextBlock is not displaying any Text althought values are being set.
Would love to get all possible suggestions. Thanks in advance.
it's one of the way to bind controls which inside datatemplate
this.DataContext = Person;
Binding binding = new Binding();
binding.Source = ob;
DataTemplate dtemp = (DataTemplate)Resources["PointTemp"];
Border bdr = dtemp.LoadContent() as Border;
TextBlock tblk = bdr.Child as TextBlock;
tblk.SetBinding(TextBlock.TextProperty, binding);
Here I used ob as double collection assign to textproperty by binding source
<UserControl.Resources>
<DataTemplate x:Key="PointTemp">
<Border Margin="0,23,0,0" Background="Transparent">
<TextBlock Text="{Binding}" Foreground="White" FontSize="28" HorizontalAlignment="Center" VerticalAlignment="Center" TextAlignment="Center"/>
</Border>
</DataTemplate>
</UserControl.Resources>
And you can assign,
if it's combobox or listbox
Here Person is a class name, or set the class name in combobox itemsource