xaml
<controls:TabControl x:Name="tabControlRoom" Grid.Row="1" Grid.Column="1" d:LayoutOverrides="Width, Height" ItemsSource="{Binding}" >
<controls:TabControl.ItemTemplate>
<DataTemplate>
<controls:TabItem Header="{Binding name}">
<StackPanel Margin="10" Orientation="Horizontal">
</StackPanel>
</controls:TabItem>
</DataTemplate>
</controls:TabControl.ItemTemplate>
</controls:TabControl>
and code
m_roomContext.Load(m_roomContext.GetRoomQuery());
tabControlRoom.DataContext = m_roomContext.Rooms;
when I open this page, then there is all the elements, but a second later I see only a white screen
error:
load operation failed for query
'GetRoom'. Unable to cast object of
type 'Web.Room' to type
'System.Windows.Controls.TabItem'/'
Create converter
public class SourceToTabItemsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
try
{
var source = (IEnumerable)value;
if (source != null)
{
var controlTemplate = (ControlTemplate)parameter;
var tabItems = new List<TabItem>();
foreach (object item in source)
{
PropertyInfo[] propertyInfos = item.GetType().GetProperties();
//тут мы выбираем, то поле которое будет Header. Вы должны сами вводить это значение.
var propertyInfo = propertyInfos.First(x => x.Name == "name");
string headerText = null;
if (propertyInfo != null)
{
object propValue = propertyInfo.GetValue(item, null);
headerText = (propValue ?? string.Empty).ToString();
}
var tabItem = new TabItem
{
DataContext = item,
Header = headerText,
Content =
controlTemplate == null
? item
: new ContentControl { Template = controlTemplate }
};
tabItems.Add(tabItem);
}
return tabItems;
}
return null;
}
catch (Exception)
{
return null;
}
}
/// <summary>
/// ConvertBack method is not supported
/// </summary>
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException("ConvertBack method is not supported");
}
Create ControlTemplate:
<ControlTemplate x:Key="MyTabItemContentTemplate">
<StackPanel>
<TextBlock Text="{Binding Path=name}" />
</StackPanel>
</ControlTemplate>
And binding convert, controltemplate
<controls:TabControl x:Name="tabControl"
ItemsSource="{Binding ElementName=tabControl,
Path=DataContext,
Converter={StaticResource ConverterCollectionToTabItems},
ConverterParameter={StaticResource MyTabItemContentTemplate}}">
</controls:TabControl>
taken from the blog binding-tabcontrol
When binding the TabControl there are two things you need to accomplish, one is the header content for the TabItem the other is the content for the selected TabItem, which is generally another user control.
The way have I've solved this in the past is to bind ItemsSource of the TabControl to a collection of view models, and to provide two data templates, one to supply the header content for the TabItem, an the other to supply the content for the selected TabItem, which maps to a view.
<Window.Resources>
<DataTemplate x:Key="ItemTemplate">
<TextBlock Text="{Binding Title}" />
</DataTemplate>
<DataTemplate x:Key="ContentTemplate">
<local:SampleView />
</DataTemplate>
</Window.Resources>
<Grid>
<TabControl
ItemsSource="{Binding SampleViewModels}"
ItemTemplate="{StaticResource ItemTemplate}"
ContentTemplate="{StaticResource ContentTemplate}"
SelectedIndex="0"
/>
</Grid>
Related
Using a wpf ListBox I'm trying to display a list of filename without displaying the full path (more convenient for user).
Data comes from an ObservableCollection which is filled using Dialog.
private ObservableCollection<string> _VidFileDisplay = new ObservableCollection<string>(new[] {""});
public ObservableCollection<string> VidFileDisplay
{
get { return _VidFileDisplay; }
set { _VidFileDisplay = value; }
}
In the end I want to select some items and get back the full file path. For this I have a converter :
public class PathToFilenameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//return Path.GetFileName(value.ToString());
string result = null;
if (value != null)
{
var path = value.ToString();
if (string.IsNullOrWhiteSpace(path) == false)
result = Path.GetFileName(path);
}
return result;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
Which I bind to my listbox itemsource :
<ListBox x:Name="VideoFileList" Margin="0" Grid.Row="1" Grid.RowSpan="5" Template="{DynamicResource BaseListBoxControlStyle}" ItemContainerStyle="{DynamicResource BaseListBoxItemStyle}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ItemsSource="{Binding Path=DataContext.VidFileDisplay, Converter={StaticResource PathToFileName},ElementName=Ch_Parameters, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding Path=SelectedVidNames,ElementName=Ch_Parameters, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
Without the converter, it is working fine (but of course this is the full path displayed in the listbox). With the converter I have one character per line... displaying this :
System.Collections.ObjectModel.ObservableCollection`1[System.String]
Where am I wrong ?
Thank you
In ItemsSource binding converter applies to the whole list and not to each item in the collection. If you want to apply your converter per item you need to do it ItemTemplate
<ListBox x:Name="VideoFileList" ItemsSource="{Binding Path=DataContext.VidFileDisplay, ElementName=Ch_Parameters}" ...>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=., Converter={StaticResource PathToFileName}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I have a wpf application ,in which I have a tabcontrol ,I set its ItemTemplate from an external resource dictionary,in that template I have an Image Control which I bind to a string Property 'ImagePath' in my ViewModel and using a converter I convert 'ImagePath' into new bitmapImage .I created my converter class instance in the external resource disctinary. I merged the external dictionary in my App.xaml
Here is my External Resource Dictionary:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helpers="clr-namespace:RH_Maize.Helper">
<helpers:ImageSourceConverter x:Key="ImageSourceConverter" />
<DataTemplate x:Key="ClosableTabItemTemplate">
<StackPanel Width="155"
Orientation="Horizontal"
Height="35">
<Image Height="25"
Width="30"
Source="{Binding Path=ImagePath,Converter={ StaticResource ImageSourceConverter}}"/>
<TextBlock TextWrapping="Wrap" Text="{Binding DisplayName}"
Width="100" Height="27" VerticalAlignment="Bottom" FontSize="15" Margin="3,0,0,0"/>
<Button Command="{Binding CloseCommand}"
Content="x" />
</StackPanel>
</DataTemplate>
Here is my Converter class :
public class ImageSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (targetType == typeof(ImageSource))
{
if (value is string)
{
string str = (string)value;
return new BitmapImage(new Uri(str, UriKind.RelativeOrAbsolute));
}
else if (value is Uri)
{
Uri uri = (Uri)value;
return new BitmapImage(uri);
}
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
My ViewModel Property is :
private string _ImagePath;
public string ImagePath
{
get
{
return _ImagePath;
}
set
{
_ImagePath = value;
RaisePropertyChanged(() => ImagePath);
}
}
My problem is that the converter classes convert method is not invoking at all .Whats wrong with my code ?
I'm sure this is probably something basic in WPF but I'm new to XAML syntax I'm trying to wrap my head around it.
The Setup
I have a LogItem Type -- just a POCO:
public class LogItem
{
public string Message {get;set;}
public Color MessageColor {get;set;}
}
and a List of LogItem in my ViewModel:
private ObservableCollection<LogItem> _logItems;
public ObservableCollection<LogItem> LogItems
{
get { return _logItems; }
set
{
if (value != _logItems)
{
_logItems = value;
OnPropertyChanged("LogItems");
}
}
}
My viewmodel is bound to the view so that I can do the following:
<ListBox Grid.Row="0" Margin="0,10,0,0" Grid.ColumnSpan="3" Height="150" ItemsSource="{Binding LogItems}">
(Obviously I still have to set the display text binding, etc.)
The Question
Given that I have a Message and MessageColor property in LogItems, what is the correct XAML syntax to bind the color of the item text to the color I specify?
<ListBox Grid.Row="0" Margin="0,10,0,0" Grid.ColumnSpan="3" Height="150" ItemsSource="{Binding LogItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Message}" Foreground="{Binding MessageColor}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
TextBlock Foreground expects a Brush not a Color. Like a lot of things in WPF, There are lot's of ways to approch this. Here is a couple:
Change to MessageColor property in your viewModel to Brush
Brush MessageColor {get;set;}
Create a SolidColorBrush and bind it to your color
<TextBlock Text="{Binding Message}">
<TextBlock.Foreground>
<SolidColorBrush Color="{Binding MessageColor}"/>
</TextBlock.Foreground>
</TextBlock>
Create a ColorToBrushConverter
public class ColorToBrushConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null) return Brushes.Black; // Default color
Color color = (Color)value;
return new SolidColorBrush(color);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
In xaml, create the converter as static resource
<Window.Resources>
<local:ColorToBrushConverter x:Key="colorToBrushConverter"/>
</Window.Resources>
use it in the binding
<TextBlock Text="{Binding Message}" Foreground="{Binding MessageColor, Converter={StaticResource colorToBrushConverter}"/>
Good luck
I have 1...n tabcontrols in my application, with the following XAML setup:
<TabControl Name="ordersTabControl" ItemsSource="{Binding CoilItems}">
<TabControl.ItemTemplate>
<DataTemplate DataType="models:Coil">
<StackPanel>
<TextBlock Text="{Binding CoilCode, StringFormat='Coil: {0}'}" />
<TextBlock Text="{Binding ArticleCode, StringFormat='Auftrag: {0}'}" />
<TextBlock Text="{Binding RestWeight, StringFormat='Restgewicht: {0} kg'}" />
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
[...]
</TabControl.ContentTemplate>
</TabControl>
The amount of open tabs changes at runtime. Now I'd like to show an index in each tab (i.e. the first tab shows "Order 1", the second "Order 2" and so on) in addition to the information already in each header.
AFAIK when using DataTemplate I can't access the tab-properties through the code-behind, so is there any way in XAML to bind a textblock inside a tabheader to show the Index of that specific tab in the tabcontrol?
I think it should be possible with RelativeSource and FindAncestors? Alas I couldn't really find any clear tutorial on those settings (and I only started using WPF 2 days ago).
I'm going to give you a solution using attached properties. Check the code:
Attached Properties
public static class IndexAttachedProperty
{
#region TabItemIndex
public static int GetTabItemIndex(DependencyObject obj)
{
return (int) obj.GetValue(TabItemIndexProperty);
}
public static void SetTabItemIndex(DependencyObject obj, int value)
{
obj.SetValue(TabItemIndexProperty, value);
}
// Using a DependencyProperty as the backing store for TabItemIndex. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TabItemIndexProperty =
DependencyProperty.RegisterAttached("TabItemIndex", typeof (int), typeof (IndexAttachedProperty),
new PropertyMetadata(-1));
#endregion
#region TrackTabItemIndex
public static bool GetTrackTabItemIndex(DependencyObject obj)
{
return (bool) obj.GetValue(TrackTabItemIndexProperty);
}
public static void SetTrackTabItemIndex(DependencyObject obj, bool value)
{
obj.SetValue(TrackTabItemIndexProperty, value);
}
// Using a DependencyProperty as the backing store for TrackTabItemIndex. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TrackTabItemIndexProperty =
DependencyProperty.RegisterAttached("TrackTabItemIndex", typeof (bool), typeof (IndexAttachedProperty),
new PropertyMetadata(false, TrackTabItemIndexOnPropertyChanged));
private static void TrackTabItemIndexOnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var tabControl = GetParent(d, p => p is TabControl) as TabControl;
var tabItem = GetParent(d, p => p is TabItem) as TabItem;
if (tabControl == null || tabItem == null)
return;
if (!(bool)e.NewValue)
return;
int index = tabControl.Items.IndexOf(tabItem.DataContext == null ? tabItem : tabItem.DataContext);
SetTabItemIndex(d, index);
}
#endregion
public static DependencyObject GetParent(DependencyObject item, Func<DependencyObject, bool> condition)
{
if (item == null)
return null;
return condition(item) ? item : GetParent(VisualTreeHelper.GetParent(item), condition);
}
}
This code define two attached properties, the first one is to set if an item tracks the the tab item index in wich it is contained. The second one is the index property.
XAML Sample code:
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type WpfApplication3:A}">
<StackPanel x:Name="tabItemRoot" WpfApplication3:IndexAttachedProperty.TrackTabItemIndex ="True">
<TextBlock Text="{Binding Text}"/>
<TextBlock Text="{Binding Path=(WpfApplication3:IndexAttachedProperty.TabItemIndex), ElementName=tabItemRoot}"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
The above code is an example of using the attached property. You may adapt to your code easy.
Result:
Hope this code works for you...
If you are not using AlternationCount property for other purposes, you can hack it for an easier solution.
bind AlternationCount like this
<TabControl AlternationCount="{Binding Path=Items.Count, RelativeSource={RelativeSource Self}}">
And then in your ItemTemplate bind the TextBlock or other control you wish to the AlternationIndex like this,
<TextBlock Text="{Binding Path=(ItemsControl.AlternationIndex), RelativeSource={RelativeSource FindAncestor, AncestorType=TabItem}}" />
With a custom converter plugged into the above binding, you can display anything you want.
Even if you had access to the just the TabItem properties in the code-behind, it wouldn't help as the tab doesn't know it's own index in the TabControl collection. This is true of all ItemsControls and seems annoying, but it makes sense because when can any object ever tell you what its own position is within a collection?
It can be done with IndexOf, however, as long as you have access to both the control's ItemsCollection and the content of the tab. We can do this in a MultiValueConverter, so that it can be done within the DataTemplate.
Converter code:
public class ItemsControlIndexConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
ItemCollection itemCollection = (ItemCollection)values[0];
return (itemCollection.IndexOf(values[1]) + 1).ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
TabControl XAML:
<TabControl ItemsSource="{Binding CoilItems}">
<TabControl.Resources>
<local:ItemsControlIndexConverter x:Key="IndexConverter"/>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource IndexConverter}" StringFormat="Order {0}" Mode="OneWay">
<Binding RelativeSource="{RelativeSource AncestorType=TabControl}" Path="Items"/> <!-- First converter index is the ItemsCollection -->
<Binding /> <!-- Second index is the content of this tab -->
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<!-- Fill in the rest of the header template -->
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
I got a couple of RibbonTab who should be selected based on the type of Content in a TabControl.
The problem is that sometimes it does not work, the error seems to occur more often on my remote Windows XP than my main computer with Windows 7 (maybe because it is slower). When the error occurs it does not help to switch tabs again and it does not work for any of the views who share the same IValueConverter.
<ribbon:RibbonWindow Title="{Binding DisplayName}"
x:Name="RibbonWindow"
x:Class="Abc.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ribbon="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"
xmlns:c="clr-namespace:Abc.Converters">
<ribbon:RibbonWindow.Resources>
<c:FoobarConverter x:Key="Foobar" />
<c:FooConverter x:Key="Foo" />
<c:BarConverter x:Key="Bar" />
</ribbon:RibbonWindow.Resources>
<DockPanel>
<ribbon:Ribbon DockPanel.Dock="Top">
<a:FoobarRibbonTab IsSelected="{Binding SelectedTab, Mode=OneWay, Converter={StaticResource Foobar}}" />
<a:FooRibbonTab IsSelected="{Binding SelectedTab, Mode=OneWay, Converter={StaticResource Foo}}" />
<a:BarRibbonTab IsSelected="{Binding SelectedTab, Mode=OneWay, Converter={StaticResource Bar}}" />
</ribbon:Ribbon>
<TabControl ItemsSource="{Binding Tabs}"
SelectedItem="{Binding SelectedTab}"/>
</DockPanel>
public class FooConverter : IValueConverter
{
public List<Type> ValidTypes = new List<Type>();
public FooConverter()
{
ValidTypes = new List<Type>
{
typeof(FooView),
// etc...
};
}
public virtual object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var tabItemBase = value as TabItemBase;
return tabItemBase != null && tabItemBase.Content != null && ValidTypes.Contains(tabItemBase.Content.GetType());
}
public virtual object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Update
Each RibbonTab has their own Converter and each View only exists in one Converter.
When I use Ctrl + Tab the error occurs earlier.