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.
Related
I have a Listbox which consists of elements that looks like this.
class ItemForListbox
{
public string path { get; set; }
public string file { get; set; }
public InstallationPackageChoice choices { get; set; }
public KeepKill keepKill { get; set; }
}
The choices and keepKill are objects.
My XAML for the listbox has this DataTemplate
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Name="btnPackageVersion" Style="{StaticResource ListBoxButtonFormat}" Content ="Package version" Click="btnInstallPackage_Click" HorizontalAlignment="Right"/>
<TextBlock Name="txtBlkPath" Text="{Binding path}" FontSize="10" Width="500" Height="20" Margin="10,10,0,0"/>
<Button Name="btnFilterPath" Style="{StaticResource ListBoxButtonFormat}" Content ="Filter path" Click="btnFilterpath_Click" HorizontalAlignment="Right"/>
<TextBlock Name="txtBlkFile" Text="{Binding file}" FontSize="10" Width="150" Height="20" Margin="10,10,0,0"/>
<Button Name="btnFilterfile" Style="{StaticResource ListBoxButtonFormat}" Content="Filter file" Click="btnFilterfile_Click" HorizontalAlignment="Right"/>
<CheckBox Name="chkBxKeep" Content="Keep" VerticalAlignment="Center" HorizontalAlignment="Right" IsChecked="True" Checked="chkBxKeep_Checked"/>
<CheckBox Name="chkBxKill" Content="Kill" VerticalAlignment="Center" HorizontalAlignment="Right" Checked="chkBxKill_Checked"/>
<Separator Name="MySeparator"
Height="3"
Width="Auto"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Background="Red" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
The two checkboxes underlying data is the killKeep object in the Listbox element object.
The problem is that i would like to have a radiobutton functionality of the two checkboxes, so when i check a checkbox, the other one unchecks. The reason i dont use radiobuttons are that i simply do not understand them in regards to binding. But now i find i also do not know how to do this binding with the checkboxes.
I hope someone can help me.
You can use binding with a converter to bind IsChecked property of checkboxes:
<Window.Resources>
<local:InverseBooleanConverter x:Key="InverseBooleanConverter"/>
</Window.Resources>
...
<CheckBox Name="chkBxKeep" Content="Keep" VerticalAlignment="Center" HorizontalAlignment="Right" IsChecked="True" Checked="chkBxKeep_Checked"/>
<CheckBox Name="chkBxKill" Content="Kill" VerticalAlignment="Center" HorizontalAlignment="Right" Checked="chkBxKill_Checked"
IsChecked="{Binding ElementName=chkBxKeep, Path=IsChecked, Converter={StaticResource InverseBooleanConverter}}"/>
Converter
public class InverseBooleanConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null && value is bool)
{
return !(bool)value;
}
return true;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return Convert(value, targetType, parameter, culture);
}
#endregion
}
I'm creating a WPF application(UI) with a TabControl which contains four TabItems in it.
My Application --
From the User Tab, I want to choose somehow (maybe with a check box, or any other way) which of the GridColumns will be displayed at the User Tab. I can work with the other Tabs, but sometimes I need to give the user the opportunity to work only with the specific outputs he/she wants. How can I make this work? I am new to C# and wpf, so if you could explain a simple solution and offer some codes, I would appriciate it.
Before answering your question, a brief hint for you: when you ask, post some code, otherwise it will be hard that someone spends time for helping you.
For implementing your application you need to consider:
ElementName data binding
ElementName data binding can't work in the ColumnDefinitions property
To solve the point number 2 you can read this very interesting article by Josh Smith.
Pratically he creates a special kind of ElementSpy with an attached property:
public class ElementSpy : Freezable
{
private DependencyObject element;
public static ElementSpy GetNameScopeSource(DependencyObject obj)
{
return (ElementSpy)obj.GetValue(NameScopeSourceProperty);
}
public static void SetNameScopeSource(DependencyObject obj, ElementSpy value)
{
obj.SetValue(NameScopeSourceProperty, value);
}
public static readonly DependencyProperty NameScopeSourceProperty =
DependencyProperty.RegisterAttached("NameScopeSource", typeof(ElementSpy), typeof(ElementSpy),
new UIPropertyMetadata(null, OnNameScopeSourceProperty));
private static void OnNameScopeSourceProperty(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
INameScope nameScope;
ElementSpy elementSpy = args.NewValue as ElementSpy;
if (elementSpy != null && elementSpy.Element != null)
{
nameScope = NameScope.GetNameScope(elementSpy.Element);
if (nameScope != null)
{
d.Dispatcher.BeginInvoke(new Action<DependencyObject, INameScope>(SetScope),
System.Windows.Threading.DispatcherPriority.Normal,
d, nameScope);
}
}
}
private static void SetScope(DependencyObject d, INameScope nameScope)
{
NameScope.SetNameScope(d, nameScope);
}
public DependencyObject Element
{
get
{
if (element == null)
{
PropertyInfo propertyInfo = typeof(Freezable).GetProperty("InheritanceContext",
BindingFlags.NonPublic | BindingFlags.Instance);
element = propertyInfo.GetValue(this, null) as DependencyObject;
if (element != null)
{
Freeze();
}
}
return element;
}
}
protected override Freezable CreateInstanceCore()
{
return new ElementSpy();
}
}
Now we can use binding in our XAML
<Window.Resources>
<local:BooleanWidthConverter x:Key="BooleanWidthConverter" />
<local:ElementSpy x:Key="ElementSpy" />
</Window.Resources>
<StackPanel HorizontalAlignment="Stretch">
<Grid local:ElementSpy.NameScopeSource="{StaticResource ElementSpy}"
Margin="0,0,0,30">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding ElementName=cb1, Path=IsChecked, Converter={StaticResource BooleanWidthConverter}, Mode=OneWay}" />
<ColumnDefinition Width="{Binding ElementName=cb2, Path=IsChecked, Converter={StaticResource BooleanWidthConverter}, Mode=OneWay}" />
<ColumnDefinition Width="{Binding ElementName=cb3, Path=IsChecked, Converter={StaticResource BooleanWidthConverter}, Mode=OneWay}" />
</Grid.ColumnDefinitions>
<TextBlock HorizontalAlignment="Center" Text="Column 1" Margin="4" Grid.Column="0" />
<TextBlock HorizontalAlignment="Center" Text="Column 2" Margin="4" Grid.Column="1" />
<TextBlock HorizontalAlignment="Center" Text="Column 3" Margin="4" Grid.Column="2" />
</Grid>
<CheckBox x:Name="cb1" Content="Column 1" Margin="4" IsChecked="true" HorizontalAlignment="Center" />
<CheckBox x:Name="cb2" Content="Column 2" Margin="4" IsChecked="true" HorizontalAlignment="Center" />
<CheckBox x:Name="cb3" Content="Column 3" Margin="4" IsChecked="true" HorizontalAlignment="Center" />
</StackPanel>
To complete our code, we need a simple converter:
public class BooleanWidthConverter : IValueConverter
{
private static GridLength star = new GridLength(1, GridUnitType.Star);
private static GridLength zero = new GridLength(0, GridUnitType.Pixel);
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool boolValue = (bool)value;
return boolValue ? star : zero;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Of course this is just a sample prototype, but I am sure it can help you with your application.
Working Example :
I have a table called groups as shown below :
After looking at the image above I think you might have understood that primary key and foreign key exist in the same table. I think this is what developers call cyclic reference.
In MainWindow.xaml I have a DataGrid which contains three columns namely Group Name, Parent Name, Description. The xaml looks like :
<Window .......>
<Window.DataContext>
<self:MainWindowViewModel />
</Window.DataContext>
<DataGrid ItemsSource="{Binding Groups}" TabIndex="1">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Group Name" Width="2*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding GroupName}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding GroupName}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Parent" Width="2*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding ParentID, Converter={StaticResource GroupIDToGroupNameConverter}}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.GroupsCollection, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
SelectedValue="{Binding ParentID}"
SelectedValuePath="GroupID"
DisplayMemberPath="GroupName"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Description" Width="2*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding Description}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</power:PowerDataGrid.Columns>
</power:PowerDataGrid>
</Window>
Now I have a ViewModel called MainWindowViewModel
public class MainWindowViewModel : INotifyPropertyChanged
{
public MainWindowViewModel()
{
SampleDBContext sampleDBContext = new SampleDBContext();
Groups = new ObservableCollection<Group>();
GroupsCollection = new ObservableCollection<Group>(from g in sampleDBContext.Groups select g);
}
private ObservableCollection<Group> _groups;
public ObservableCollection<Group> Groups
{
get
{
return _groups;
}
set
{
_groups = value;
OnPropertyChanged("Groups");
}
}
private ObservableCollection<Group> _groupsCollection;
public ObservableCollection<Group> GroupsCollection
{
get
{
return _groupsCollection;
}
set
{
_groupsCollection = value;
OnPropertyChanged("GroupsCollection");
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertryName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertryName));
}
}
#endregion
}
GroupIDToGroupName.cs //Converter
public class GroupIDToGroupName : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
{
SampleDBContext sampleDBContext = new SampleDBContext();
return (from g in sampleDBContext.Groups
where g.GroupID == (int)value
select g.GroupName).FirstOrDefault();
}
else
{
return "";
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
SampleDBContext sampleDBContext = new SampleDBContext();
return (from g in sampleDBContext.Groups
where g.GroupName == (string)value
select g.GroupID).FirstOrDefault();
}
}
In App.xaml :
<self:GroupIDToGroupName x:Key="GroupIDToGroupNameConveerter" />
My Case (Very similar to above sample):
I just want to use a Multi-Column ComboBox instead of simple ComboBox inside DataGrid.
I have two tables :
Now I have set up my code exactly as the above mentioned code.
I have added an extra class called GroupIDAndNameWithCorrespondingEffect like :
public class GroupIDAndNameWithCorrespondingEffect : INotifyPropertyChanged
{
private int _groupID;
public int GroupID
{
get
{
return _groupID;
}
set
{
_groupID = value;
OnPropertyChanged("GroupID");
}
}
private string _groupName;
public string GroupName
{
get
{
return _groupName;
}
set
{
_groupName = value;
OnPropertyChanged("GroupName");
}
}
private string _correspondingEffect;
public string CorrespondingEffect
{
get
{
return _correspondingEffect;
}
set
{
_correspondingEffect = value;
OnPropertyChanged("CorrespondingEffect");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Changes in my ViewModel :
I removed the property GroupsCollection and all its references and added a new property called GroupIDAndNamesWithCorrespondingEffects as below :
private ObservableCollection<GroupIDAndNameWithCorrespondingEffect> _groupIDAndNamesWithCorrespondingEffects;
public ObservableCollection<GroupIDAndNameWithCorrespondingEffect> GroupIDAndNamesWithCorrespondingEffects
{
get
{
return _groupIDAndNamesWithCorrespondingEffects;
}
set
{
_groupIDAndNamesWithCorrespondingEffects = value;
OnPropertyChanged("GroupIDAndNamesWithCorrespondingEffects");
}
}
And in the Constructor :
List<GroupIDAndNameWithCorrespondingEffect> _GroupIDAndNamesWithCorrespondingEffects = (
from g in sampleDBContext.Groups
select new GroupIDAndNameWithCorrespondingEffect
{
GroupID = g.GroupID,
GroupName = g.GroupName,
CorrespondingEffect = g.Effect.Effect1
}
).ToList();
GroupIDAndNamesWithCorrespondingEffects
= new ObservableCollection<GroupIDAndNameWithCorrespondingEffect>(
_GroupIDAndNamesWithCorrespondingEffects.Where
(
u => !GetAllChildren(25)
.Select(x => x.GroupID)
.Contains(u.GroupID)
).ToList()
);
In my MainWindow.xaml I have added a resource as follows :
<Window.Resources>
<CollectionViewSource x:Key="GroupNamesWithCorrespondingEffectsCollection" Source="{Binding GroupIDAndNamesWithCorrespondingEffects}" />
</Window.Resources>
Inside Grid's Resources :
<Grid.Resources>
<CompositeCollection x:Key="Items">
<ComboBoxItem IsEnabled="False" Background="#FF2A2A2A" Foreground="White">
<Grid TextElement.FontWeight="Bold" >
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="A" />
<ColumnDefinition Width="50" />
<ColumnDefinition SharedSizeGroup="B" />
</Grid.ColumnDefinitions>
<Grid.Children>
<TextBlock Grid.Column="0" Text="Group Name" />
<TextBlock Grid.Column="2" Text="Effect" />
</Grid.Children>
</Grid>
</ComboBoxItem>
<CollectionContainer Collection="{Binding Source={StaticResource GroupNamesWithCorrespondingEffectsCollection}}" />
</CompositeCollection>
<DataTemplate DataType="{x:Type self:GroupIDAndNameWithCorrespondingEffect}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="A" />
<ColumnDefinition Width="50" />
<ColumnDefinition SharedSizeGroup="B" />
</Grid.ColumnDefinitions>
<Grid.Children>
<TextBlock Grid.Column="0" Text="{Binding GroupName}" />
<TextBlock Grid.Column="2" Text="{Binding CorrespondingEffect}" />
</Grid.Children>
</Grid>
</DataTemplate>
</Grid.Resources>
And I changed the ItemsSource of ComboBox to ItemsSource="{DynamicResource Items}".
Problems :
When I run the program ComboBox displays all the items correctly. Also, two columns with headers are displayed. Its working fine, but when I press Enter or TAB, then focus remains in the same cell and the comboBox's text displays namespace of GroupIDAndNameWithCorrespondingEffect
Here is the image of Problem :
Sample:
Incase if somebody wants to check the sample then it is available here. And database files are available here.
Got it!!!!
I declared the resource with key="Items" in Grid. So, when I Check for ComboBox.SelectedIndex in PreviewKeyDown event of DataGrid, it gives me -1 and so my logic works as unexpected. Also, at this time I get ComboBox.Items.Count = 0.
So I just changed the place of declaration of the resource. I mean I deleted Grid.Resources Section and in ComboBox.Resources section I wrote the same code. Now it is working fine. Now in PreviewKeyDown of DataGrid, I get the expected SelectedIndex of the ComboBox as well as ComboBox.Items.Count is equal to the Count in Source.
I don't know why this happens? As I have used it as DynamicResource I expect it to work even if it is declared in Grid.Resources section.
Maybe ... maybe your combobox just needs IsEditable = false. IsEditable = True causes comboboxes to show namespaces instead of the text.
One way to solve this is in http://www.shujaat.net/2010/08/wpf-editable-combobox-with-datatemplate.html
TextSearch.TextPath="GroupName"
Another way would be to provide a DataTemplate to the ComboBox's ItemTemplate
<ComboBox ItemTemplate="{StaticResource myDataTemplate}"/>
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();
}
}
If I have this structure:
public class Parent
{
public string Name{get; set;}
public List<Child> Childs {get; set;}
}
public class Child
{
public string Name{get; set;}
public string Value{get; set;}
public enum ValueType {get; set;}
}
public enum ValueType
{
Int,
Boolean,
String
};
public class ParentFactory
{
public List<Parent> Parents {get; set;}
public ParentFactory()
{
Child child1 = new Child() {Name="Filter1", Value="1", ValueType=ValueType.String};
Child child2 = new Child() {Name="isExecuted", Value="true", ValueType=ValueType.Boolean};
Child child3 = new Child() {Name="Width", Value="10", ValueType=ValueType.Int};
Parent parent1 = new Parent(){Name="Adam", Childs = new List<Child>(){child1, child2}};
Parent parent2 = new Parent(){Name="Kevin", Childs = new List<Child>(){child3}};
Parents = new List<Parent>(){parent1, parent2};
}
}
I want to bind the object: ParentFactory parentFactory = new ParentFactory() to ItemsControl:
<DockPanel>
<ItemsControl ItemsSource="{Binding Parents}">
</ItemsControl>
</DockPanel>
<Window.Resources>
<DataTemplate DataType="{x:Type Parent}">
<StackPanel Margin="2,2,2,1">
<Expander Header="{Binding Name}">
<ItemsControl ItemsSource="{Binding Childs}" />
</Expander>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type Child}">
<StackPanel>
<TextBox Grid.Column="0" Text="{Binding Name}" />
<TextBox Grid.Column="1" Text="{Binding Value}"/>
<TextBox Grid.Column="2" Text="{Binding ValueType}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
In the Stackpanel, there are one control: TextBox. However, I want it to be more dynamic: if the ValueType is a Boolean then use a Checkbox and else use a Textbox for the: <TextBox Grid.Column="1" Text="{Binding Value}"/>.
Is this possible, and if so, how can I achieve it?
You can change the DataTemplate dynamically
Xaml
<DataTemplate>
<DataTemplate.Resources>
<DataTemplate x:Key="Condition1">
<TextBox></TextBox> // Do you binding
</DataTemplate>
<DataTemplate x:Key="Condition2">
<CheckBox></CheckBox> // Do you binding
</DataTemplate>
</DataTemplate.Resources>
</DataTemplate>
<ContentPresenter x:Name="ContentField"
Content="{Binding}"
ContentTemplate="{StaticResource ResourceKey=Condition1}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=ValueType}" Value="1">
<Setter TargetName="ContentField" Property="ContentTemplate" Value="{StaticResource ResourceKey=Condition2}" />
</DataTrigger>
</DataTemplate.Triggers>
Be sure to set the Bindings correctly ... and make the DataTemplates for Condition1 and Condition2
hope it helps :)
I think the easiest way to do it is to have both of them in your panel and define two new boolean properties that will control their Visibility through a Boolean To Visibility ValueConverter.
public bool IsValueTypeBoolean
{
get
{
...Conditions that will return true for CheckBox.
}
}
public bool IsValueTypeOther
{
get
{
return !this.IsValueBoolean;
}
}
<TextBox Grid.Column="2" Visibility="{Binding IsValueTypeOther, Converter={StaticResource visibilityConverter}}"/>
<CheckBox Grid.Column="2" Visibility="{Binding IsValueTypeBoolean, Converter={StaticResource visibilityConverter}}"/>
Boolean To VisibilityConverter
[ValueConversion(typeof(bool), typeof(Visibility))]
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool myValue = (bool)value;
if (myValue)
return Visibility.Visible;
else
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Add this as a resource to your XAML:
<local:BooleanToVisibilityConverter x:Key="visibilityConverter"></local:VisibilityConverter>
I hope there are no typos...