WPF ComboBox as System.Windows.Media.Colors - c#

Wanting to get the color combobox (see image) behavior in my WPF ListView column.
Can someone help me get this started? I am comfortable with ListView binding but not sure how to implement this.
EDIT:
xmlns:System="clr-namespace:System;assembly=mscorlib"
<ObjectDataProvider MethodName="GetValues"
ObjectType="{x:Type System:Enum}"
x:Key="ColorList">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="Windows.Media.Color" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
Tells me type provided must be an enum.
Best Answer I have found:
How can I list colors in WPF with XAML?

ComboBox with ItemTemplate
You will have to use ItemTemplate for you ComboBox items:
<ComboBox ItemsSource="{Binding NamedColors}"
xmlns:converters="clr-namespace:TestLab.WPF">
<ComboBox.Resources>
<converters:ColorToSolidBrushConverter x:Key="ColorToBrush"/>
</ComboBox.Resources>
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Border BorderThickness="0" Height="20" Width="20"
Background="{Binding Value, Converter={StaticResource ColorToBrush}}"/>
<TextBlock Text="{Binding Key}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Brush converter
Also, you will need a color to brush converter, because binding doesn't do it automatically:
public class ColorToSolidBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new SolidColorBrush((Color)value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Color name - color pair creation
And here is how you can create the color Name - color pairs (currently it is an instance method in the main Window class, but you can refactor it to some helper class):
private IEnumerable<KeyValuePair<String, Color>> GetColors()
{
return typeof(Colors)
.GetProperties()
.Where(prop =>
typeof(Color).IsAssignableFrom(prop.PropertyType))
.Select(prop =>
new KeyValuePair<String, Color>(prop.Name, (Color)prop.GetValue(null)));
}
Window code
And this is the window:
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.NamedColors = this.GetColors();
this.DataContext = this;
}
public IEnumerable<KeyValuePair<String, Color>> NamedColors
{
get;
private set;
}
}
ObjectDataProvider
Some code file:
public namespace SomeNamespace
{
public static class ColorHelper
{
public static IEnumerable<KeyValuePair<String, Color>> GetColors()
{
return typeof(Colors)
.GetProperties()
.Where(prop =>
typeof(Color).IsAssignableFrom(prop.PropertyType))
.Select(prop =>
new KeyValuePair<String, Color>(prop.Name, (Color)prop.GetValue(null)));
}
}
}
XAML object data provider:
...
xmlns:someNamespace="clr-namespace:SomeNamespace"
...
<ObjectDataProvider MethodName="GetColors"
ObjectType="{x:Type someNamespace.ColorHelper}"
x:Key="ColorList">
</ObjectDataProvider>
XAML comboBox:
<ComboBox ItemsSource="{Binding ColorList}" ...

Should be something like this :
<ComboBox ItemsSource="{Binding ItemSourceObs}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Rectangle Fill="{Binding Color}" Height="10" Width="10" />
<TextBlock Text="{Binding DisplayedText}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Where DisplayesText and Color (type of Brushes) are properties of an object lets say A and ItemSourceObs is ObservableCollection of type A
This approach is based on MVVM pattern
Using code behind working solution :
<ComboBox x:Name="ComboColor" Width="50" Height="50" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Rectangle Fill="{Binding Name}" Width="16" Height="16" Margin="0,2,5,2" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
And code behind :
public MainWindow()
{
InitializeComponent();
ComboColor.ItemsSource = typeof(Colors).GetProperties();
}

Related

WPF - How to implement two-way data binding with the dynamically created control?

I'm writing a program that dynamically creates Control based on the data type of the properties extracted using reflection. Here is the view in subject for examination.
<ListView ItemsSource="{Binding PropertyControls}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="8">
<TextBlock Text="{Binding PropertyName}" FontSize="14" Width="400"></TextBlock>
<UserControl FontSize="14" Content="{Binding Path=PropertyValue, Converter={StaticResource PropertyValueConverter}}"></UserControl>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I created an item template for the items in ListView. Each row consists of two elements; the label and the dynamically created control.
For instance, if the PropertyValue is a boolean, then the dynamically created control will be a checkbox. If the PropertyValue is a string, then the dynamically created control will be a TextBox. If the PropertyValue is a list of FileInfo, then a separate window will be created with another ListView and browse button with OpenFileDialog.
I was able to accomplish the dynamically created control by creating a class that implements IValueConverter and which is utilized as specified in the XAML. The PropertyValueConverter converts the PropertyValue into a dynamically created control by inspecting its data type.
My problem is when the CheckBox is checked, there was no event raised and the ViewModel is not modified by its changes. I suspect because the binding in the XAML was made to the UserControl and not to its child control which happens to be a CheckBox. Although it is possible to bind the IsChecked programmatically in the PropertyValueConverter, is there a better way to solve this?
------- Revision 1 -------
public class PropertyControl: INotifyPropertyChanged
{
public string PropertyName { get; set; }
private object propertyValue;
public object PropertyValue
{
get { return propertyValue; }
set
{
propertyValue = value;
OnPropertyChanged(nameof(PropertyValue));
}
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
/// <summary>
/// Dynamically converts between value and control given a data type - control mapping.
/// </summary>
class PropertyValueConverter: IValueConverter
{
/// <summary>
/// Converts from value to control.
/// </summary>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType == typeof (int))
return new NumberTextBox {Text = value.ToString()};
if (targetType == typeof (string))
return new TextBox {Text = value.ToString()};
if (targetType == typeof (bool))
return new CheckBox {IsChecked = (bool) value};
throw new Exception("Unknown targetType: " + targetType);
}
/// <summary>
/// Converts from control to value.
/// </summary>
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType == typeof (NumberTextBox))
return (value as NumberTextBox).Value;
if (targetType == typeof(TextBox))
return (value as TextBox).Text;
if (targetType == typeof(CheckBox))
return (value as CheckBox).IsChecked;
throw new Exception("Unknown targetType: " + targetType);
}
}
------- Revision 2 -------
public partial class SettingsWindow : Window
{
public BindingList<SettingViewModel> ViewModels { get; set; }
private SettingsManager settingsManager = new SettingsManager(new SettingsRepository());
public SettingsWindow()
{
InitializeComponent();
// Reloads the data stored in all setting instances from database if there's any.
settingsManager.Reload();
// Initialize setting view model.
ViewModels = SettingViewModel.GetAll(settingsManager);
}
private void ResetButton_OnClick(object sender, RoutedEventArgs e)
{
settingsManager.Reload();
}
private void SaveButton_OnClick(object sender, RoutedEventArgs e)
{
settingsManager.SaveChanges();
}
}
--- Tab Control ---
<TabControl Name="ClassTabControl" TabStripPlacement="Left" ItemsSource="{Binding ViewModels}">
<TabControl.Resources>
<utilities:PropertyValueConverter x:Key="PropertyValueConverter" />
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}"
Margin="8" FontSize="14"></TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListView ItemsSource="{Binding PropertyControls}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="8">
<TextBlock Text="{Binding PropertyName}" FontSize="14" Width="400"></TextBlock>
<CheckBox FontSize="14" IsChecked="{Binding Path=PropertyValue, Converter={StaticResource PropertyValueConverter}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></CheckBox>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Orientation="Horizontal" Grid.Row="1" Margin="8" HorizontalAlignment="Center">
<Button Name="ResetButton" Padding="4" Content="Reset" FontSize="14" Margin="4"
Click="ResetButton_OnClick"></Button>
<Button Name="SaveButton" Padding="4" Content="Save" FontSize="14" Margin="4"
Click="SaveButton_OnClick"></Button>
</StackPanel>
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
A much easier way is to create templates based on the type of your property. First of all you have to add the system namespace to access all the basic types:
xmlns:System="clr-namespace:System;assembly=mscorlib"
Now you can get rid of your converter and do it all in XAML like:
<DataTemplate>
<StackPanel x:Name="itemStackPanel" Orientation="Horizontal" Margin="8">
<!-- General part -->
<TextBlock Text="{Binding PropertyName}" FontSize="14" Width="400"/>
<!-- Specific (property based) part -->
<ContentPresenter Content="{Binding PropertyValue}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type System:String}">
<TextBlock Text="{Binding ElementName=itemStackPanel, Path=DataContext.PropertyValue}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type System:Boolean}">
<CheckBox IsChecked="{Binding ElementName=itemStackPanel, Path=DataContext.PropertyValue}"/>
</DataTemplate>
<!-- ... -->
</ContentPresenter.Resources>
</ContentPresenter>
</StackPanel>
</DataTemplate>
You simply create a template for every possible type like you need it. The ContentPresenter selects the right template based on the type of PropertyValue. Since you are going to bind to a parent from out of your template you have to use a element to bind on PropertyValue (described in Access parent DataContext from DataTemplate).
/edit Ok, some one was faster :/
Here's the example (without INotifyPropertyChanged because I did not want to write too much code ;))
public interface IViewModel
{
string PropertyName { get; set; }
}
public class StringViewModel : IViewModel
{
public string PropertyName { get; set; }
public string Content { get; set; }
}
public class BooleanViewModel : IViewModel
{
public string PropertyName { get; set; }
public bool IsChecked { get; set; }
}
public class MainViewModel
{
public ObservableCollection<IViewModel> ViewModels { get; set; }
public MainViewModel()
{
ViewModels = new ObservableCollection<IViewModel>
{
new BooleanViewModel {PropertyName = "Bool", IsChecked = true },
new StringViewModel {PropertyName = "String", Content = "My text"}
};
}
}
<Window x:Class="WpfApplication2.MainWindow"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication2"
mc:Ignorable="d"
xmlns:viewModel="clr-namespace:WpfApplication2"
Title="MainWindow">
<Grid>
<ListView ItemsSource="{Binding ViewModels}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
Margin="8">
<TextBlock Text="{Binding PropertyName}" />
<ContentControl FontSize="14" Content="{Binding .}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type viewModel:StringViewModel}">
<TextBox Text="{Binding Content}" />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:BooleanViewModel}">
<CheckBox IsChecked="{Binding IsChecked}" />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>

Populate combox with a type

I'm working on a new application that use a lot of reflection and have some problem to populate a combobox.
I have a parameter class with some information about a parameter for specific method:
class Parameter
{
public string Name { get; set; }
public Type Type { get; set; }
public object Value { get; set; }
}
I also have a view with a DataTemplateSelector that pick different DataTemplated depending on the parameter type:
class ParameterDataTemplateSelector : DataTemplateSelector
{
public DataTemplate StringDataTemplate { get; set; }
public DataTemplate BoolDataTemplate { get; set; }
public DataTemplate EnumDataTemplate { get; set; }
public DataTemplate NullTypeDataTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var parameter = item as Parameter;
if (parameter.Type == null)
{
return NullTypeDataTemplate;
}
if (parameter.Type == typeof (int)
|| parameter.Type == typeof(string))
{
return StringDataTemplate;
}
if (parameter.Type.BaseType == typeof (Enum))
{
return EnumDataTemplate;
}
return BoolDataTemplate;
}
}
And in my XAML I have my different templates:
<UserControl x:Class="Stuff.UI.Views.ParametersView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dataTemplateSelectors="clr-namespace:Stuff.UI.Common.DataTemplateSelectors"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<DataTemplate x:Key="StringDataTemplate">
<StackPanel>
<Label Content="{Binding Name}"/>
<TextBox Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="BoolDataTemplate">
<StackPanel>
<CheckBox Content="{Binding Name}" IsChecked="{Binding Value}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="EnumDataTemplate">
<StackPanel>
<ComboBox ItemsSource="{Binding >>something<<}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="NullDataTemplate">
<StackPanel>
<Label Content="NULL TYPE"/>
</StackPanel>
</DataTemplate>
<dataTemplateSelectors:ParameterDataTemplateSelector
StringDataTemplate="{StaticResource StringDataTemplate}"
BoolDataTemplate="{StaticResource BoolDataTemplate}"
EnumDataTemplate="{StaticResource EnumDataTemplate}"
NullTypeDataTemplate="{StaticResource NullDataTemplate}"
x:Key="ParameterDataTemplateSelector"/>
</UserControl.Resources>
<Grid x:Name="MainGrid">
<StackPanel Background="#dddddd">
<Label Content="Parameters" Background="#e1db45"></Label>
<ScrollViewer VerticalScrollBarVisibility="Auto" Height="Auto" Margin="10">
<ItemsControl ItemsSource="{Binding Parameters}" ItemTemplateSelector="{StaticResource ParameterDataTemplateSelector}">
</ItemsControl>
</ScrollViewer>
</StackPanel>
</Grid>
</UserControl>
So the problem is my combobox here:
<DataTemplate x:Key="EnumDataTemplate">
<StackPanel>
<ComboBox ItemsSource="{Binding >>something<<}"/>
</StackPanel>
</DataTemplate>
How can I populate it when I only have a type (and I know by checking that it is a Enum type)? Or should I do it in code-behind in some way? I'm a bit lost here and pretty new to MVVM and WPF in general.
You could use a converter to get the list of enum values from the enum type. Something like this:
<ComboBox ItemsSource="{Binding Type,Converter={StaticResource EnumToValuesConverter}}">
<ComboBox.Resources>
<ResourceDictionary>
<local:EnumToValuesConverter x:Key="EnumToValuesConverter" />
</ResourceDictionary>
</ComboBox.Resources>
</ComboBox>
The converter should just call "GetEnumValues" on the enum type:
public class EnumToValuesConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var enumType = (Type)value;
return enumType.GetEnumValues();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Try to look at this:
Bind to an enumeration
You should use ObjectDataProvider.

WPF ComboBox Conditional ItemTemplate

I have a ComboBox Binded to a List of Possible values is the ViewModel.
<ComboBox ItemsSource="{Binding PossibleOperands}" SelectedValue="{Binding Operand, Mode=TwoWay}" VerticalAlignment="Center" Foreground="Black" FontSize="13" FontFamily="Calibri" Height="23" Grid.Column="1" Margin="7,2,0,2"/>
For most values I want the display a simple string (in The example above, "Operand Name"),
but for one of the values, I want to display a string + 2 comboBoxes.
Mockup:
Use ItemTemplateSelector class as described in MSDN
You can define a single ItemTemplate with a textblock and 2 combobox. Bind the Visibility of ComboBoxes with the required property. This way only for certain items the ComboBoxes will be visible.
I have done something like that before:
Assuming you have a class like this: (for the ComboBox's Content)
public class Content
{
private String _Texty;
public String Texty
{
get{return _Texty;}
set { _Texty = value; }
}
public List<String> Comb1{get ; set;}
public List<String> Comb2 { get; set; }
public Content(string t, List<String> comb1, List<String> comb2)
{
Texty = "Some Text";
Comb1 = comb1;
Comb2 = comb2;
}
}
You will need a Converter, defined in your xaml part like this:
<utils:ContentToVisibleConverter x:Key="MyConverter" />
where utils is something like:
xmlns:utils="clr-namespace:YOUR CONVERTER CLASS NAMESPACE"
So, define your Combobox like this in the xaml:
<ComboBox x:Name="Combo" Margin="4"
Height="23"
Width="250" ItemsSource="{Binding Collection}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<TextBlock Text="{Binding Texty}" Margin="4,0"/>
<Grid Margin="20 0 0 0">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Entity Type:"
Visibility="{Binding Path=Comb1, Converter={StaticResource MyConverter}}" />
<ComboBox Grid.Column="1" ItemsSource="{Binding Comb1}" Width="80" Margin="4,0"
Visibility="{Binding Path=Comb1, Converter={StaticResource MyConverter}}"/>
<TextBlock Grid.Column="2" Text="Entity:"
Visibility="{Binding Path=Comb2, Converter={StaticResource MyConverter}}"/>
<ComboBox Grid.Column="3" ItemsSource="{Binding Comb2}" Width="80" Margin="4,0"
Visibility="{Binding Path=Comb2, Converter={StaticResource MyConverter}}"/>
</Grid>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Somewhere in my code, I use a Binding Collection this way:
private ObservableCollection<Content> _Collection;
public ObservableCollection<Content> Collection
{
get { return _Collection; }
set { _Collection = value; NotifyPropertyChanged("Collection"); }
}
// Fill it like this:
Collection = new ObservableCollection<Content>();
Collection.Add(new Content("Some Stuff", null, null));
Collection.Add(new Content("Some Stuff", null, null));
Collection.Add(new Content("Some Stuff", null, null));
Collection.Add(new Content("Some Stuff",
new List<String>() { "One", "Two" },
new List<String>() { "One", "Two" }));
Finally, I define a class for the converter it only contains:
public class ContentToVisibleConverter : System.Windows.Markup.MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var s = value as List<string>;
if (s != null)
return s.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
else return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
That's all, let me know if it worked for you.

How to Toggle the Visibility of an Item in a StackPanel

I have a ListBox which contains a StackPanel of two items, an Image and a Textblock. At the user's request, I would like to be able to toggle the visibility of the TextBlock on or off, thus only showing the Images. As it is now, the Image and TextBlock combination for each item is stacked vertically, and the Image is a perfect square (which ultimately creates a rectangular shape when the TextBlock is shown under each image). When the user wishes to hide the TextBlock, I would like to have the ListBox show only the StackPanel items as uniform squares for the Images (hopefully that made sense).
What I have is as follows
<ListBox Name="ListBoxEffects" SelectionMode="Single" ItemsSource="{Binding}" Margin="{Binding}"
toolkit:TiltEffect.IsTiltEnabled="True" SelectionChanged="ListBox_SelectionChanged"
ItemContainerStyle="{StaticResource ListBoxItemStyle1}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel ItemWidth="159" ItemHeight="Auto" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" >
<Image Source="{Binding Thumbnail}" Width="155" Height="155" />
<TextBlock Text="{Binding Name}" Visibility="{Binding TextBlockVisibility}" TextWrapping="Wrap" FontSize="{StaticResource PhoneFontSizeNormal}" VerticalAlignment="Center" HorizontalAlignment="Center" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And in the ApplicationBar created in the code behind is where I have a menu item which will allow a user to select his or her preference on showing or hiding the TextBlock under each Image
private void BuildLocalizedApplicationBar()
{
ApplicationBar = new ApplicationBar();
ApplicationBarMenuItem showFilterNamesMenuItem = new ApplicationBarMenuItem();
if (Settings.ShowFilterNames.Value)
showFilterNamesMenuItem.Text = "Hide names";
else
showFilterNamesMenuItem.Text = "Show names";
showFilterNamesMenuItem.Click += showFilterNamesMenuItem_Click;
ApplicationBar.MenuItems.Add(showFilterNamesMenuItem);
}
void showFilterNamesMenuItem_Click(object sender, EventArgs e)
{
if(Settings.ShowFilterNames.Value)
{
((ApplicationBarMenuItem)ApplicationBar.MenuItems[0]).Text = "Hide names";
Settings.ShowFilterNames.Value = false;
//Toggle the text block visibility to show text here
}
else
{
((ApplicationBarMenuItem)ApplicationBar.MenuItems[0]).Text = "Show names";
Settings.ShowFilterNames.Value = true;
//Toggle the text block visibility to hide text here
}
}
And a check is performed when the page is navigated to so that the TextBlock's under each image can be shown or hidden appropriately
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (Settings.ShowFilterNames.Value)
//Show the TextBlocks here
else
//Hide the TextBlocks here
}
As far as I can tell the above implementation does toggle the menu item text correctly and saves the user's preference so that upon returning the menu item text is displayed according to the last selection the user chose, but I am unsure of how to change the visibility of the TextBlock underneath each image in the ListBox?
EDIT**
BooleanToVisibilityConverter.cs
//Error on BooleanToVisibilityConverter stating does not implement interface member 'System.Windows.Data.IValueConverter.Convert(object, System.Type, object, System.Globalization.CultureInfo)
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo language)
{
return (value is bool && (bool)value) ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo language)
{
return value is Visibility && (Visibility)value == Visibility.Visible;
}
}
and in XAML
xmlns:common="clr-namespace:TestApp.Common"
<phone:PhoneApplicationPage.Resources>
<common:BooleanToVisibilityConverter x:Key="BoolToVisConv" />
</phone:PhoneApplicationPage.Resources>
<ListBox Name="ListBoxEffects" SelectionMode="Single" ItemsSource="{Binding}" Margin="{Binding}"
toolkit:TiltEffect.IsTiltEnabled="True" SelectionChanged="ListBox_SelectionChanged"
ItemContainerStyle="{StaticResource ListBoxItemStyle1}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel ItemWidth="159" ItemHeight="Auto" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" >
<Image Source="{Binding Thumbnail}" Width="155" Height="155" />
<TextBlock Text="{Binding Name}" Visibility="{Binding IsTextBlockVisible, Converter={StaticResource BoolToVisConv}}" TextWrapping="Wrap" FontSize="{StaticResource PhoneFontSizeNormal}" VerticalAlignment="Center" HorizontalAlignment="Center" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Use this method to find out your Textblock for each ListBoxItem
public static T FindFirstElementInVisualTree<T>(DependencyObject parentElement) where T : DependencyObject
{
try
{
int childCount = VisualTreeHelper.GetChildrenCount(parentElement);
if (childCount == 0)
return null;
for (int i = 0; i < childCount; i++)
{
var child = VisualTreeHelper.GetChild(parentElement, i);
if (child != null && child is T)
{
return (T)child;
}
else
{
var result = FindFirstElementInVisualTree<T>(child);
if (result != null)
return result;
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return null;
}
This Method will return First element of specified type from your DataTemplate. And allows you to work with that indivisual element.
For this You can use following code snippet
for(i=0;i<ListBoxEffects.count;i++)
{
ListBoxItem item = ListBoxEffects.ItemContainerGenerator.ContainerFromIndex(i) as ListBoxItem;
StackPanel TargetStackPanel = common.FindFirstElementInVisualTree<StackPanel>(item);
TextBlock TargetTextBlock= TargetStackPanel.Children[1] as TextBlock;
TargetTextBlock.Visibility = Visibility.Visible;
ListBoxEffects.UpdateLayout();
}
Use the above code to show or hide the textblocks respectively Just by changing the line
TargetTextBlock.Visibility = Visibility.Visible;
or
TargetTextBlock.Visibility = Visibility.Collapsed;
The Visibility property is an enum of type Visibility. This would be quite a bit easier if it were a boolean, but it's not.
You should define a static resource to instantiate a BooleanToVisibility converter, then bind the Visibility property to a boolean property in your DataContext. Here's a working example:
<Window x:Class="WpfApplication4.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">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConv" />
</Window.Resources>
<StackPanel>
<TextBlock Text="Hide me" Visibility="{Binding IsTextBlockVisible, Converter={StaticResource BoolToVisConv}}" />
<Button Content="Toggle TextBlock" Name="ToggleItButton" Click="ToggleItButton_Click" />
</StackPanel>
public partial class MainWindow : Window, INotifyPropertyChanged {
private bool m_IsTextBlockVisible = true;
public bool IsTextBlockVisible {
get { return m_IsTextBlockVisible; }
set { m_IsTextBlockVisible = value; NotifyPropertyChanged("IsTextBlockVisible"); }
}
public MainWindow() {
InitializeComponent();
DataContext = this;
}
private void ToggleItButton_Click(object sender, RoutedEventArgs e) {
IsTextBlockVisible = !IsTextBlockVisible;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name) {
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
You can create two DataTemplate. One DataTemplate with name and the other one without name.
XAML:
<Window ...
>
<Window.Resources>
<DataTemplate x:Key="TemplateWithName">
<StackPanel Orientation="Vertical" >
<Image Source="{Binding Thumbnail}" Width="155" Height="155" />
<TextBlock Text="{Binding Name}" TextWrapping="Wrap" VerticalAlignment="Center" HorizontalAlignment="Center" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="TemplateWithoutName">
<StackPanel Orientation="Vertical" >
<Image Source="{Binding Thumbnail}" Width="155" Height="155" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel>
<ToggleButton x:Name="tbName" Content="Name" Click="tbName_Click" />
</StackPanel>
<ListBox Name="ListBoxEffects" SelectionMode="Single" ItemsSource="{Binding}"
Grid.Row="1" ItemTemplate="{StaticResource TemplateWithName}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel ItemWidth="159" ItemHeight="Auto" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
</Window>
Code-behind:
private void tbName_Click(object sender, RoutedEventArgs e)
{
if (tbName.IsChecked.Value)
{
ListBoxEffects.ItemTemplate = this.FindResource("TemplateWithoutName") as DataTemplate;
}
else
{
ListBoxEffects.ItemTemplate = this.FindResource("TemplateWithName") as DataTemplate;
}
}
Matthew, addition to your question:
public class BooleanToVisibilityConverter : IValueConverter
{
private object GetVisibility(object value)
{
if (!(value is bool))
return Visibility.Collapsed;
bool objValue = (bool)value;
if (objValue)
{
return Visibility.Visible;
}
return Visibility.Collapsed;
}
public object Convert(object value, Type targetType, object parameter, string language)
{
return GetVisibility(value);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}

TreeView with multiple leaf data types in WPF

I am trying to create a TreeView with the following hierarchy:
Device1
--File1
--File2
--Hook1
--Hook2
Device2
--File1
--File1
Device3
--Hook1
So basically The Root level node is a device with children being File and Hook. I have created the following tree with the hierarchical data template
<TreeView ItemsSource="{Binding Devices}">
<HierarchicalDataTemplate DataType="{x:Type datamodel:Device}" ItemsSource="{Binding Files}">
<TextBlock Margin="5,5,0,0" Text="{Binding DeviceName}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type datamodel:File}">
<TextBlock Margin="5,0,0,0" Text="{Binding FileName}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
Class Devices
{
public string DeviceName
{
get;set;
}
public string List<File> Files
{
get;set;
}
public string List<Hook> Hooks
{
get;set;
}
}
public class File
{
public string FileName
{
get;set;
}
public string FileType
{
get;set;
}
public string Location
{
get; set;
}
}
public class Hook
{
public string HookName
{
get;set;
}
public string Type
{
get;set;
}
}
I am able to add only one Datatemplate in the ItemTemplate of the HierarchicalDataTemplate. How do I specify two data types under a single HierarchicalDataTemplate??
You will have to use the converter here which will return the CompositeCollection which will contain both files and hooks. You will use this converter with ItemsSource of HierarchicalDataTemplate DataType="{x:Type datamodel:Device}"
Then you just need to define two datatemplates for File and Hook data type.
Thanks
I tried with the CompositeCollection but realized that the bound objects were converted to base types. So I used it in combination of DataTemplateSelector.
The following solution worked for me.
<TreeView ItemsSource="{Binding Devices}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type datamodel:Devices}" ItemTemplateSelector="{StaticResource LeafDataTemplateSelector}">
<HierarchicalDataTemplate.ItemsSource>
<MultiBinding Converter="{StaticResource CompositeCollectionConverter}">
<Binding Path="Files" />
<Binding Path="Hooks"/>
</MultiBinding>
</HierarchicalDataTemplate.ItemsSource>
<StackPanel Height="25" Orientation="Horizontal">
<Image Height="20" Width="20" Source="Device.png"/>
<TextBlock Margin="5,5,0,0" Text="{Binding Device}"/>
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate x:Key="FileKey" DataType="{x:Type datamodel:File}">
<StackPanel Height="25" Orientation="Horizontal" ToolTip="Installation File">
<Image Height="20" Width="20" Source="File.png" />
<TextBlock Text="{Binding FileName}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="HookKey" DataType="{x:Type datamodel:Hook}">
<StackPanel Height="25" Orientation="Horizontal" ToolTip="Installation Hook">
<Image Height="20" Width="20" Source="Hook.png" />
<TextBlock Margin="5,5,0,0" Text="{Binding HookName}"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
The have used the template selector to select the appropriate template based on the key.
public class LeafDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate
SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null)
{
if (item is InstallationManifesFile)
return
element.FindResource("FileKey")
as DataTemplate;
else if (item is InstallationManifestHook)
return element.FindResource("HookKey")
as DataTemplate;
}
return null;
}
}
public class CompositeCollectionConverter : IMultiValueConverter
{
public object Convert(object[] values
, Type targetType
, object parameter
, System.Globalization.CultureInfo culture)
{
var res = new CompositeCollection();
foreach (var item in values)
if (item is IEnumerable && item != null)
res.Add(new CollectionContainer()
{
Collection = item as IEnumerable
});
return res;
}
public object[] ConvertBack(object value
, Type[] targetTypes
, object parameter
, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}

Categories

Resources