I have ComboBox with custom ComboBoxItem. ComboBoxItem contains TextBlock and CheckBox. CheckBox bind to bool property in my ViewModel. When i click to CheckBox, all works fine, but when i click to TextBlock - ComboBox Closing! I need 'MultiSelection' mode inside my ComboBox
xaml:
<ComboBox ItemsSource="{Binding Elements}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="{Binding Caption}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
VM:
public class SomeClass : INotifyPropertyChanged
{
private string m_caption;
private bool m_isSelected;
public string Caption
{
get { return m_caption; }
set
{
if (m_caption != value)
{
m_caption = value;
RaisePropertyChanged("Caption");
}
}
}
public bool IsSelected
{
get { return m_isSelected; }
set
{
if (m_isSelected != value)
{
m_isSelected = value;
RaisePropertyChanged("IsSelected");
}
}
}
public SomeClass(string _caption)
{
if (string.IsNullOrWhiteSpace(_caption))
m_caption = Guid.NewGuid().ToString();
Caption = _caption;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string _property)
{
PropertyChangedEventHandler hanlder = PropertyChanged;
if (hanlder != null)
{
hanlder.Invoke(null, new PropertyChangedEventArgs(_property));
}
}
}
Without a lot of tweaking, you can't really display a multi-selection in a closed combobox, so I guess this part is not really needed. So I propose you use a button and a popup, where the popup is containing a listview with multi selection enabled for your items.
As Button, I use DropDownButton from Extended WPF Toolkit, you can also implement it differently.
Introducing the Extended WPF Toolkit namespace:
xmlns:xt="http://schemas.xceed.com/wpf/xaml/toolkit"
The actual thing:
<xt:DropDownButton Content="Elements Selection" VerticalAlignment="Top" HorizontalAlignment="Left" MinWidth="100">
<xt:DropDownButton.DropDownContent>
<ListView ItemsSource="{Binding Elements}" SelectionMode="Multiple" MinWidth="100">
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType=ListViewItem}}">
<CheckBox.Content>
<TextBlock Text="{Binding Caption}"/>
</CheckBox.Content>
</CheckBox>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</xt:DropDownButton.DropDownContent>
</xt:DropDownButton>
Another important little detail to reflect the property changes in WPF: your property changed notifications need a source instead of null!
protected void RaisePropertyChanged(string _property)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler.Invoke(this, new PropertyChangedEventArgs(_property));
}
}
Bind the Content property of the CheckBox to your Caption property and use an ItemContainerStyle that stretches the ComboBoxItem container:
<ComboBox ItemsSource="{Binding Elements}">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ComboBox.ItemContainerStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Content="{Binding Caption}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Related
I'm making a color selector combobox with checkboxes.
Combobox's visible Text is "Colors", and items are the names of colors with a checkbox.
Scenario: open combobox -> select whatever in comboboxItems -> get checked(Selected)items.
So, when the user clicks a combobox item, I want to change the checkbox value and keep the combobox opened.
I'm stuck with the checking (selecting) items functionality.
How to do this?
Model:
public class ColorItem : DependencyObject
{
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register
("Name", typeof(string), typeof(ColorItem),
new PropertyMetadata(string.Empty));
public static readonly DependencyProperty IsSelectedProperty =
DependencyProperty.Register
("IsSelected", typeof(bool), typeof(ColorItem),
new PropertyMetadata(true));
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
public bool IsSelected
{
get { return (bool)GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); }
}
}
XAML:
<ComboBox Name="ColorCombobox" IsEditable="True" IsReadOnly="True" Focusable="False" StaysOpenOnEdit="True"
ItemsSource="{Binding ColorItems}" SelectionChanged="OnComboboxSelectionChanged" Text="Colors" Margin="0,0,30,0" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsSelected}" Width="20" />
<TextBlock Text="{Binding ColorName}" Width="100" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
SelectionChanged Event handler in code-behind:
private void OnComboboxSelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
ComboBox box = sender as ComboBox;
box.Text = "Colors"; //Not works. Text will empty be ""
}
Get rid of the StackPanel and the TextBlock and define an ItemContainerStyle that stretches the content of the ComboBoxItem:
<ComboBox Name="ColorCombobox" IsEditable="True" IsReadOnly="True" Focusable="False" StaysOpenOnEdit="True"
ItemsSource="{Binding ColorItems}" SelectionChanged="OnComboboxSelectionChanged" Text="Colors" Margin="0,0,30,0" >
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ComboBox.ItemContainerStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected}" Content="{Binding Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
On a UWP app (Windows 10), I am displaying a list of records in a ListView.
When I click on an item, its StackPanel is displayed (using INotifyPropertyChanged).
In the StackPanel, there is a TextBox with some data populated via binding.
I would like that the TextBox automatically receives the focus whenever the StackPanel becomes visible, but I can't find which property or event to use, and how to trigger a textBox.Focus().
Thanks for your feedback on this !
The DataTemplate:
<DataTemplate x:Key="templateList">
<StackPanel>
...
<StackPanel Visibility="{Binding IsSelected}">
<TextBox x:Name="textBox"
Text="{Binding Title, Mode=TwoWay}"/>
...
</StackPanel>
</StackPanel>
</DataTemplate>
...
The ListView:
<ListView x:Name="listView"
ItemsSource="{Binding mylist}"
ItemTemplate="{StaticResource templateList}"/>
I can suggest use Behaviors for this case. As I noticed, you use Visibility type for IsSelected property. It means that we can use DataTriggerBehavior and create our SetupFocusAction which implement IAction
public class SetupFocusAction : DependencyObject, IAction
{
public Control TargetObject
{
get { return (Control)GetValue(TargetObjectProperty); }
set { SetValue(TargetObjectProperty, value); }
}
public static readonly DependencyProperty TargetObjectProperty =
DependencyProperty.Register("TargetObject", typeof(Control), typeof(SetupFocusAction), new PropertyMetadata(0));
public object Execute(object sender, object parameter)
{
return TargetObject?.Focus(FocusState.Programmatic);
}
}
After that we can use this action in XAML:
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
...
<StackPanel Visibility="{Binding IsSelected}"
Grid.Row="1">
<TextBox x:Name="textBox"
Text="{Binding Title, Mode=TwoWay}">
<i:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding IsSelected}"
ComparisonCondition="Equal"
Value="Visible">
<local:SetupFocusAction TargetObject="{Binding ElementName=textBox}"/>
</core:DataTriggerBehavior>
</i:Interaction.Behaviors>
</TextBox>
</StackPanel>
I'm trying to set item disabled for ComboBox, I have my item model:
public class PermissionsViewItem
{
public string Title { get; set; }
public bool IsEnabled { get; set; }
}
And ComboBox defined:
<ComboBox Background="WhiteSmoke" Margin="65,308,0,0" BorderThickness="0" Width="220" Padding="0" Foreground="#FF7B7A7F" ItemsSource="{Binding PermissionsViewItems}" >
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="local:PermissionsViewItem">
<StackPanel >
<Grid>
<Border Background="{x:Null}" BorderThickness="0" HorizontalAlignment="Left" VerticalAlignment="Center">
<TextBlock Text="{x:Bind Title}" FontWeight="SemiBold" />
</Border>
</Grid>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
However there seems to be no way to set item disabled manually, but there is ComboBoxItem element generated (I can see it in LiveVisualTree) which has IsEnabled property and it works. I can access it via Styling
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem" >
<Setter Property="IsEnabled" Value="False"/>
</Style>
</ComboBox.ItemContainerStyle>
This would disable every item, but unfortunately ItemContainerStyle does not bind to item, because it has context of ComboBox not PermissionsViewItem so I cannot utilize PermissionsViewItem.IsEnabled property here.
Is there is any way to disable specific item (even a hacky way will suffice)?
You can override the combobox as follows and set the bindings at run time. This works for me
public class ZCombobox:ComboBox
{
protected override void PrepareContainerForItemOverride(Windows.UI.Xaml.DependencyObject element, object item)
{
ComboBoxItem zitem = element as ComboBoxItem;
if (zitem != null)
{
Binding binding = new Binding();
binding.Path = new PropertyPath("IsSelectable");
zitem.SetBinding(ComboBoxItem.IsEnabledProperty, binding);
}
base.PrepareContainerForItemOverride(element, item);
}
}
Bind IsEnabled property in TextBlock.
<TextBlock Text="{x:Bind Title}" FontWeight="SemiBold" IsEnabled="{Binding IsEnabled}" />
I have TabControl with two items.
<Window x:Class="WpfApplication1.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">
<Grid>
<TabControl>
<TabItem Header="Tab1" Content="{Binding}" ContentTemplate="{StaticResource Tab1}"/>
<TabItem Header="Tab2" ContentTemplate="{StaticResource Tab2}"/>
</TabControl>
</Grid>
</Window>
There are DataGrid control in one tabItem of TabControl. DataTemplates of TabItems:
<DataTemplate x:Key="Tab1">
<DataGrid ItemsSource="{Binding Entities}" x:Name="dataGridEx">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" Header="Name" Width="100"/>
<DataGridTemplateColumn Header="Position" Width="150">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Position}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox SelectedItem="{Binding Position, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding Path=DataContext.Positions, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
<DataTemplate x:Key="Tab2">
<Grid>
<TextBox Text="Empty tab"></TextBox>
</Grid>
</DataTemplate>
DataContext of MainWindow:
public class MainWindowViewModel
{
public ObservableCollection<Entity> Entities { get; set; }
public List<string> Positions { get { return new List<string>() { "Manager", "Seller" }; } }
public MainWindowViewModel()
{
Entities = new ObservableCollection<Entity>()
{
new Entity() {Name = "John", Position = "Manager"},
new Entity() {Name = "Mark", Position = "Seller"},
new Entity() {Name = "Alice"}
};
}
}
The Entity class:
public class Entity : INotifyPropertyChanged
{
private string _name;
private string _position;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public string Position
{
get { return _position; }
set
{
_position = value;
OnPropertyChanged("Position");
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
#endregion
}
Application is running. I edit the Position column. Then I switch to the 2nd tab and then the 1st tab again. Position value of edited row is deleted.
If I write data directly to tabItems - it works okay
<Grid>
<TabControl>
<TabItem Header="Tab1">...</TabItem>
<TabItem Header="Tab2">...</TabItem>
</TabControl>
</Grid>
But I need to use DataTemplates for tabItems in my solution.
I have some idea.. Using style, not datatemplate inside editing mode
<DataGridComboBoxColumn Header="Position" SelectedItemBinding="{Binding Position, UpdateSourceTrigger=PropertyChanged}">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.Position, ElementName=dataGridEx}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.Variables, ElementName=dataGridEx}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
In this way everything works okay.
But I have custom control like IntelliSense instead of ComboBox. It is requires to use DataGridTemplateColumn with DataTemplates for CellTemplate and CellEditingTemplate. What Should I do in this case? Maybe I need to create custom DataGridComboBoxColumn?
Can you help me with my issue?
I have TreeView structure defined as below:
<TreeView ItemsSource="{Binding RootCollection}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ChildNodes}">
<TextBlock Foreground="Green" Text="{Binding Text}" />
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ChildNodes}">
<TextBlock Foreground="Black" Text="{Binding Text}" />
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
as you can see root elements have green foreground and all childrens are black
Now I want to change the foreground color of childrens from black to red on those childnodes whose parents have "Failed" property set to true.
For example if RootCollection[0].Failed = true then all RootCollection[0].ChildNodes on treeview should become red (rootnode stays green, and there wouldn't be any grandchildnodes in this case so it doesn't matter what will happen to them).
I tried setting DataTrigger Styles wherever I could and tried binding to RelativeSource in many different ways but I couldn't handle it.
Any help will be appreciated :)
DataContext is something like this:
public class MyTreeNode : INotifyPropertyChanged
{
private string text;
private ObservableCollection<MyTreeNode> childNodes;
private bool failed;
public string Text
{
get
{
return this.text;
}
set
{
this.text = value;
Changed("Text");
}
}
public bool Failed
{
get
{
return this.failed;
}
set
{
this.failed = value;
Changed("Text");
}
}
public ObservableCollection<MyTreeNode> ChildNodes
{
get
{
return this.childNodes;
}
set
{
this.childNodes = value;
Changed("ChildNodes");
}
}
private void string Changed(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public ObservableCollection<MyTreeNode> RootCollection { get; set; }
public MyClass()
{
this.RootCollection = new ObservableCollection<MyTreeNode>
{
new MyTreeNode
{
Text = "first",
Failed = true,
ChildNodes = new ObservableCollection<MyTreeNode>
{
new MyTreeNode { Text = "first first" },
new MyTreeNode { Text = "first second" }
}
},
new MyTreeNode
{
Text = "second",
ChildNodes = new ObservableCollection<MyTreeNode>
{
new MyTreeNode { Text = "second first" }
}
}
};
}
it's simplified because my real code uses many custom classes.
Here's view structure from Snoop:
http://i.imgur.com/XMF6rLN.png
(I need to set property marked in blue based on property marked in red)
I can't check if this works at the moment, so it might not, but it looks about right. Try this:
<HierarchicalDataTemplate ItemsSource="{Binding ChildNodes}">
<TextBlock Text="{Binding Text}">
<TextBlock.Style>
<Style>
<Setter Property="Foreground" Value="Black" />
<Style.Triggers>
<DataTrigger Binding="{Binding Failed, RelativeSource={
RelativeSource AncestorType={x:Type TreeViewItem}, AncestorLevel=1}}" Value="True">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</HierarchicalDataTemplate>
If it doesn't work, you could experiment a little with the AncestorLevel property, setting it to 2 or 3, or even removing it. Let me know how it goes.
you can use template selector instead
//you put this in your App.xaml
<Application.Resources>
<HierarchicalDataTemplate x:Key="failed" ItemsSource="{Binding ChildNodes}">
<TextBlock Name="pTxt" Foreground="Red" Text="{Binding Text}" >
</TextBlock>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ChildNodes}">
<TextBlock Foreground="Black" Text="{Binding Text}" />
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="succeded" ItemsSource="{Binding ChildNodes}">
<TextBlock Name="pTxt" Foreground="Green" Text="{Binding Text}" >
</TextBlock>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ChildNodes}">
<TextBlock Foreground="Black" Text="{Binding Text}" />
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</Application.Resources>
// and here in your Window.cs
public class ResourceInstDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate
SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null && item is MyTreeNode)
{
MyTreeNode treeNode = item as MyTreeNode;
DataTemplate temp = null;
if (treeNode.Failed)
temp = App.Current.Resources["failed"] as HierarchicalDataTemplate;
else
temp = App.Current.Resources["succeded"] as HierarchicalDataTemplate;
return temp;
}
return null;
}
}
// and this in you window.xaml.cs
<TreeView Name="treeView" ItemsSource="{Binding RootCollection}">
<TreeView.ItemTemplateSelector>
<myApp:ResourceInstDataTemplateSelector></myApp:ResourceInstDataTemplateSelector>
</TreeView.ItemTemplateSelector>
</TreeView>
I tried it and it's work perfectly
Hope this help