I have a Collection of Model-objects in my ViewModel. I would like to be able to bind a TabControl to these and use a DataTemplate to extract the information from the Model-objects. When I try to do this I get the errormessage: Unable to cast object of type Model to object of type TabItem. After spending some time looking for a solution I found the following:
The Silverlight TabControl is
broken. Use a combination of ListBox
and ContentControl to mimic the
behaviour of a TabControl. (Means
that I have to skin the ListBox to
look like a TabControl)
TabControl does not override
PrepareContainerForItemOverride and
the solution is to make a
Converter. (Not so good because I
then need to specify the type of the
convertee in the Converter)
Anyone know any better solution?
XAML
<sdk:TabControl ItemsSource="{Binding Items, ElementName=MyControl}">
<sdk:TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</sdk:TabControl.ItemTemplate>
</sdk:TabControl>
C#
public ObservableCollection<Model> Items { get; set; }
public ViewModel()
Items = new ObservableCollection<Model>{
new Model { Name = "1"},
new Model { Name = "2"},
new Model { Name = "3"},
new Model { Name = "4"}
};
}
Suggested Converter:
public class TabConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
List<TabSource> source = value as List<TabSource>;
if (source != null)
{
List<TabItem> result = new List<TabItem>();
foreach (TabSource tab in source)
{
result.Add(new TabItem()
{
Header = tab.Header,
Content = tab.Content
});
}
return result;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
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
Related
I have an enum where some of the items have a DescriptionAttribute set.
I want to in my WPF app have a dropdown where the user can select an items from the enum, but I want the dropdown to utilize the Description value if it's available.
I wrote code to get the list of values (pull the description if available, else use name), and I have the XAML I'm trying to use for the Object Provider, but it doesn't populate anything.
The XAML works if I use GetValues with ObjectType Definition.
C#
public static string[] GetDescriptions(Enum enumType)
{
List<string> descriptions = new List<string>();
Type t = enumType.GetType();
foreach(string name in Enum.GetNames(t))
{
FieldInfo field = t.GetField(name);
object[] d = field.GetCustomAttributes(typeof(DescriptionAttribute), true);
if (d.Any())
{
descriptions.Add(((DescriptionAttribute)d[0]).Description);
}
else
{
descriptions.Add(name);
}
}
return descriptions.ToArray();
}
XAML:
<ObjectDataProvider x:Key="SkillEnum" MethodName="KwCommon:EnumExtensions.GetDescriptions" >
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:SkillLevels"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
I do something similar to bind an enum to a combobox. I have a couple of helper functions:
public static string GetEnumDescription<TEnum>(this TEnum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes != null && attributes.Length > 0)
{
return attributes[0].Description;
}
else
{
return value.ToString();
}
}
and this function that turns the enum into a list of keyvaluepairs:
public static IEnumerable<KeyValuePair<string, Enum>> GetEnumList(this Enum t, bool useDescription = true)
{
return Enum.GetValues(t.GetType()).Cast<Enum>().Select(e => new KeyValuePair<string, Enum>(useDescription == true ? e.GetEnumDescription() : e.ToString(), e)).ToList();
}
I create a converter (In my case markupextension, here is a link for that markupextension)
public class EnumToListConverter : ConverterMarkupExtension<EnumToListConverter>
{
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool useDescription = true;
if (parameter is bool p)
{
useDescription = p;
}
if (value is Enum e)
{
return EnumHelper.GetEnumList(e, useDescription);
}
return DependencyProperty.UnsetValue;
}
public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
Then in my xaml I use it like this:
<ComboBox Grid.Column="1" Grid.Row="1" ItemsSource="{Binding Applicant.RentOrOwn, Converter={local:EnumToListConverter}, Mode=OneTime}" SelectedValuePath="Value" DisplayMemberPath="Key" SelectedValue="{Binding Applicant.RentOrOwn, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True, ValidatesOnNotifyDataErrors=True}"></ComboBox>
In this example I have an Applicant class which has a RentOrOwn enum property. This will show all the values available in the enum, and then update the RentOrOwn property when the user clicks the new selected value in an MVVM style.
I have a Control that I want to automatically disappear if another control has no visibile children. I'm not sure how to implement that though. I feel as though I need to create a binding that returns bindings for each child element's visible property and then aggregates them into a MultiValueConverter. I think it is working but it seems as though when I add items to my collection, the collection binding isn't being re-evaluated. Has anyone done this before?
Below is my code:
<Grid.Resources>
<local:BindingExpander x:Key="BindingExpander"/>
<local:TestConverter x:Key="TestConverter" />
</Grid.Resources>
<Button Content="Button" HorizontalAlignment="Left" Margin="237,166,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click">
<Button.Visibility>
<MultiBinding Converter="{StaticResource TestConverter}">
<Binding ElementName="lstItems" Path="Items" Converter="{StaticResource BindingExpander}" ConverterParameter="Visibility"/>
</MultiBinding>
</Button.Visibility>
</Button>
<ListBox x:Name="lstItems" HorizontalAlignment="Left" Height="100" Margin="601,130,0,0" VerticalAlignment="Top" Width="100" DisplayMemberPath="Content"/>
and:
public class TestConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
var ret = Visibility.Collapsed;
foreach (var item in values) {
if(item is IEnumerable IE) {
foreach (var Child in IE) {
}
}
}
return ret;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
public class BindingExpander : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
var ret = new List<Binding>();
if(value is IEnumerable IE) {
foreach (var item in IE) {
ret.Add(new Binding(parameter.ToString()) {
Source = item,
Mode = BindingMode.OneWay
});
}
}
return ret;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
````
I have a Control that I want to automatically disappear if another
control has no visibile children..
Simply create a Boolean property which reports the status of what the other control is binding to such as:
public bool HasItems { get { return _SomeArray?.Any(); }}
This property can be as elaborate as needed, but a basic one above for the example is shown.
Then bind the visibility flag of the control in question to the HasItems.
Note that the HasItems does not have the plumbing for INotifyPropertyChanged. In the code(s) where items are added to the _SomeArray simply put in a call to PropertyChanged("HasItems")
On my blog I provide a basic example of that (Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding) which looks like this where someone would bind to IsMemebershipAtMax such as what you are doing:
public bool IsMembershipAtMax
{
get { return MemberCount > 3; }
}
public int MemberCount
{
get { return _MemberCount; }
set
{
_MemberCount = value;
OnPropertyChanged();
OnPropertyChanged("IsMembershipAtMax");
}
}
public List<string> Members
{
get { return _Members; }
set { _Members = value; OnPropertyChanged(); }
}
im rookie with wpf + mvvm, have a simple mui:ModernTab control with items harcoded.
<mui:ModernTab Layout="List" SelectedSource="/Pages/Settings/Appearance.xaml">
<mui:ModernTab.Links>
<mui:Link DisplayName="appearance" Source="/Pages/Settings/Appearance.xaml" />
<mui:Link DisplayName="about" Source="/Pages/Settings/About.xaml" />
</mui:ModernTab.Links>
</mui:ModernTab>
I want populate it tab with the dbdata on the constructor of viewModel something like this on xaml code:
<ScrollViewer>
<mui:ModernTab Layout="List" Links="{Binding AllowedViews}" />
</ScrollViewer>
on viewModel c# constructor as:
public class ApplicationViewModel:ViewModelBase
{
private LinkCollection allowedViews;
public LinkCollection AllowedViews
{
get { return allowedViews; }
set {
allowedViews = value;
NotifyPropertyChanged("tabitem");
}
}
public ApplicationViewModel()
{
allowedViews.Add(new Link() { DisplayName = "item1"});
allowedViews.Add(new Link() { DisplayName = "item2" });
allowedViews.Add(new Link() { DisplayName = "item3" });
}
//allowedViews.Add(new Link() { DisplayName = "Otra Ventana", Source = new Uri("/Views/ModernWindow1.xaml", UriKind.RelativeOrAbsolute) });
}
Questions:
1-is better use a LinkCollection or List to populate data.
The right way to do the binding is with prop Links on xaml?
someone can sahre any documentation or example?
Thanks a lot. excuse my english.
public LinkCollection AllowedViews
{
get { return allowedViews; }
set {
allowedViews = value;
NotifyPropertyChanged("tabitem");
}
}
This "tabitem" should be "AllowedViews", right?
Here is a definition of dynamic links
<mui:ModernTab Layout="List" Links ="{Binding MyIEnumerable, Converter={StaticResource myCollectionToLinksConverter}}">
<mui:ModernTab.ContentLoader>
<app:MyControlLoader />
</mui:ModernTab.ContentLoader>
</mui:ModernTab>
then add a definition of a converter to your window or control
<UserControl.Resources>
<MyCollectionToLinksConverter x:Key="myCollectionToLinksConverter"/>
</UserControl.Resources>
then add the converter class
public class MyCollectionToLinksConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var source = (ICollection<MyCollectionItem>)value;
return new LinkCollection(source.Select(i => new Link() {DisplayName = i.Name, Source = new Uri(v.i, UriKind.Relative)}));
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
then add your content Loader
class MyControlLoader: DefaultContentLoader
{
protected override object LoadContent(Uri uri)
{
var myTarget = UIModel.Instance.GetMyTargetObjectById(v => v.Name == uri.OriginalString);
return new YourTabContentControl() {DataContext = myTarget};
}
}
I have a binding list of Models and a DataGrid. Value can be of couple of data types (bool, double).
public class Model
{
public object Value { get; set; }
}
public void Initialize()
{
var models = new BindingList<Model>();
models.Add(new Model(){ Value = "hello"});
models.Add(new Model(){Value=true});
signals.ItemsSource = models;
}
I want to display the data in the data grid and I want to use textbox for numbers, but combo(true/false) for boolean values.So I implemented bool2string converter and DataTemplateSelector. In my example I have one text column and one template column displaying the same data. When I start the application the combo values are not initialized (nothing is selected). Once I start playing with values, everything works,the values are properly synchronized (if I change value in one column, it will propagate to the other column). Do you have any idea, what mistake I'm doing?
public class BoolToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ((bool)value == true) ? "true" : "false";
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var comboValue = (value as ComboBoxItem).Content.ToString();
return (String.Compare(comboValue, "true", StringComparison.InvariantCultureIgnoreCase) == 0);
}
}
public class DynamicDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var element = container as FrameworkElement;
var signal = item as Model;
if (element != null && signal != null)
{
if (signal.Value is bool)
return element.FindResource("BoolTemplate") as DataTemplate;
else
return element.FindResource("TextTemplate") as DataTemplate;
}
return null;
}
}
My xaml looks like following:
<Window.Resources>
<comboQuestion:DynamicDataTemplateSelector x:Key="DataTemplateSelector"/>
<comboQuestion:BoolToStringConverter x:Key="BoolToStringConverter"/>
</Window.Resources>
<Grid>
<DataGrid Grid.Row="1" Name="signals" AutoGenerateColumns="False" ItemsSource="{Binding}" >
<DataGrid.Resources>
<DataTemplate x:Key="BoolTemplate">
<ComboBox SelectedItem="{Binding Value, Converter={StaticResource BoolToStringConverter}, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
<ComboBoxItem>true</ComboBoxItem>
<ComboBoxItem>false</ComboBoxItem>
</ComboBox>
</DataTemplate>
<DataTemplate x:Key="TextTemplate">
<TextBox Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Header="TextValue" Binding="{Binding Value}"/>
<DataGridTemplateColumn Header="DynamicValue" CellTemplateSelector="{StaticResource DataTemplateSelector}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
EDIT:
I tried to change the SelectedItem to SelectedValue and I also tried to change the Convert part of the converter to:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var cbi = new ComboBoxItem();
cbi.Content = ((bool)value == true) ? "true" : "false";
return cbi;
}
however, the behavior remains the same.
You can convert a boolean to an index number using the following converter:
public class BooleanToIndexConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((bool)value == true) ? 0 : 1;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((int)value == 0) ? true : false;
}
}
Then bind to SelectedIndex on the ComboBox:
SelectedIndex="{Binding Value, Converter={StaticResource BooleanToIndexConverter}}
I have a combobox for selecting media types. I would like mediaelement's height to change when .vmw, .mpeg or .avi files are selected. How can I achieve this with MVVM approach?
Thanks in advance
You could bind the Width and Height of the MediaElement directly to its Source property with an appropriate converter, which selects the proper size depending on the media type:
<MediaElement
Width="{Binding Path=Source, RelativeSource={RelativeSource Self}, Converter={StaticResource MediaElementSizeConverter}, ConverterParameter=Width}"
Height="{Binding Path=Source, RelativeSource={RelativeSource Self}, Converter={StaticResource MediaElementSizeConverter}, ConverterParameter=Height}"/>
The converter:
public class MediaElementSizeConverter : IValueConverter
{
private const double defaultWidth = 320d;
private const double defaultHeight = 240d;
private const double wmvWidth = 640d;
private const double wmvHeight = 480d;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Uri source = value as Uri;
if (source != null)
{
if (source.AbsolutePath.EndsWith(".wmv"))
{
return (parameter as string) == "Width" ? wmvWidth : wmvHeight;
}
// more media types ...
}
return (parameter as string) == "Width" ? defaultWidth : defaultHeight;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
One solution would be to bind the ComboBox to a list of self created MediaTypeDefinition classes.
public class MediaTypeDefinition
{
public string Name { get; set; }
public int Height { get; set; }
}
You can then bind the SelectedItem to the height of the media element.
<ComboBox x:Name="mediaTypeList" ItemsSource="{Binding Definitions}" SelectedValuePath="Name" />
<MediaElement Height="{Binding SelectedItem.Height, Elementname=mediaTypeList}" />