I want a popup to open directly above/under a selected row in a DataGrid.
Currently I have:
<DataGrid x:Name="myDataGrid">...</DataGrid>
<Popup IsOpen="{Binding ShowPopup}"
PlacementTarget="{Binding ElementName=myDataGrid}"
Placement="Bottom"
StaysOpen="False"
PopupAnimation="Slide"
AllowsTransparency="True"
FocusManager.IsFocusScope="False">
I guess, that I have to set a Path to the PlacementTarget Binding. Since SelectedCells[0] (as a path) does not work, I'm looking for the correct PlacementTarget.
Thanks
I never solved this. But now, I tried to simulate your problem.
So I have DataGrid with DataGridRow Style
<Style x:Key="DataGridRowStyle1" TargetType="{x:Type DataGridRow}">
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="Validation.ErrorTemplate" Value="{x:Null}"/>
<Setter Property="ValidationErrorTemplate">
<Setter.Value>
<ControlTemplate>
<TextBlock Foreground="Red" Margin="2,0,0,0" Text="!" VerticalAlignment="Center"/>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridRow}">
<Border x:Name="DGR_Border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
<SelectiveScrollingGrid>
<SelectiveScrollingGrid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</SelectiveScrollingGrid.ColumnDefinitions>
<SelectiveScrollingGrid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</SelectiveScrollingGrid.RowDefinitions>
Here is the popup. By PlacementRectangle and Placement you can adjust the positioning.
<Popup IsOpen="{Binding IsSelected}" Placement="Left" PlacementRectangle="20,20,0,0" >
<TextBlock Text="Yessss this is my popup" Background="AliceBlue" Foreground="Black" />
</Popup>
<DataGridCellsPresenter Grid.Column="1" ItemsPanel="{TemplateBinding ItemsPanel}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
<DataGridDetailsPresenter Grid.Column="1" Grid.Row="1" SelectiveScrollingGrid.SelectiveScrollingOrientation="{Binding AreRowDetailsFrozen, ConverterParameter={x:Static SelectiveScrollingOrientation.Vertical}, Converter={x:Static DataGrid.RowDetailsScrollingConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" Visibility="{TemplateBinding DetailsVisibility}"/>
<DataGridRowHeader Grid.RowSpan="2" SelectiveScrollingGrid.SelectiveScrollingOrientation="Vertical" Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.Row}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
</SelectiveScrollingGrid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsNewItem" Value="True">
<Setter Property="Margin" Value="{Binding NewItemMargin, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
</Trigger>
</Style.Triggers>
</Style>
Well, this Style add to your DataGrid.Resources segment and DataGrid should looks like this
<DataGrid x:Name="peopleDataGrid" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding People}" SelectionChanged="peopleDataGrid_SelectionChanged" RowStyle="{DynamicResource DataGridRowStyle1}" >
Now the code behind. Im using class Person as item I want to display
public class Person:INotifyPropertyChanged
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
private bool _IsSelected;
public bool IsSelected
{
get { return _IsSelected; }
set {
_IsSelected = value;
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("IsSelected"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
The Property IsSelected is important. It is binded to the IsOpen property of popoup.
And the code behind of the MainWindow
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
Do not forget assign DataContext to this in constructor -> this.DataContext = this;
public List<Person> People
{
get {
List<Person> retVal = new List<Person>();
retVal.Add(new Person() { Id = 1, FirstName = "John", LastName = "Lenon" });
retVal.Add(new Person() { Id = 2, FirstName = "Ringo", LastName = "Star" });
retVal.Add(new Person() { Id = 3, FirstName = "Paul", LastName = "Mc Cartney" });
retVal.Add(new Person() { Id = 4, FirstName = "George", LastName = "Harrison" });
return retVal;
}
}
Here is the event handler, that sets IsSelected property for each selected item and unselected items
private void peopleDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (object ro in e.RemovedItems)
{
Person rp = ro as Person;
if(rp != null)
rp.IsSelected = false;
}
foreach (object so in e.AddedItems)
{
Person sp = so as Person;
if (sp != null)
sp.IsSelected = true;
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Hope, this helps.
Related
Hi I'm building a control I need to access checkbox in the code behind But I get the null error
this is my control
<Style TargetType="TreeViewItem" x:Key="CheckTreeViewItem" >
<Setter Property="IsExpanded" Value="{Binding Path=IsExpanded,Mode=TwoWay}"/>
</Style>
<Style TargetType="local:CheckTreeView">
<Setter Property="ItemContainerStyle" Value="{StaticResource CheckTreeViewItem}"/>
<Setter Property="ItemTemplate">
<Setter.Value>
<HierarchicalDataTemplate DataType="{x:Type local:CheckTreeSource}" ItemsSource="{Binding Children}">
<CheckBox x:Name="PART_CheckBox" Margin="1" IsChecked="{Binding IsChecked}">
<TextBlock Text="{Binding Text}"/>
</CheckBox>
</HierarchicalDataTemplate>
</Setter.Value>
</Setter>
</Style>
And this is how I access control
[TemplatePart(Name = CheckBox_Key, Type = typeof(CheckBox))]
public partial class CheckTreeView : TreeView
{
private const string CheckBox_Key = "PART_CheckBox";
CheckBox checkBox;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
checkBox = GetTemplateChild(CheckBox_Key) as CheckBox;
checkBox.Click += CheckBox_Click;
}
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
}
}
When I use the following code I get no null error but no control in runtime
static CheckTreeView()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CheckTreeView),
new FrameworkPropertyMetadata(typeof(CheckTreeView)));
}
To subscribe to Click event of each CheckBox:
public partial class CheckTreeView : TreeView
{
public CheckTreeView()
{
InitializeComponent();
processNode(this);
}
void processNode(ItemsControl node)
{
node.ItemContainerGenerator.StatusChanged += (sender, args) =>
{
ItemContainerGenerator itemContainerGenerator = ((ItemContainerGenerator)sender);
if (itemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
for (int i = 0; i < itemContainerGenerator.Items.Count; i++)
{
TreeViewItem treeViewItem =
(TreeViewItem) itemContainerGenerator.ContainerFromIndex(i);
treeViewItem.Loaded += (o, eventArgs) =>
{
CheckBox checkBox = FindVisualChild<CheckBox>(treeViewItem);
checkBox.Click += CheckBox_Click;
};
processNode(treeViewItem);
}
}
};
}
public static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
{
if (obj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var child = VisualTreeHelper.GetChild(obj, i);
if (child is T)
{
return (T)child;
}
T childItem = FindVisualChild<T>(child);
if (childItem != null) return childItem;
}
}
return null;
}
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
}
}
Note that if you use ObservableCollection as items source, you should process changes in that collection: subscribe to events of added items, unsubscribe from events of removed items.
CheckBox_Key element is not a part of CheckTreeView.ControlTemplate. It will be a part in tree view nodes, multiple times. GetTemplateChild won't find it
I would say that intead of CheckBox in ItemTemplate for CheckTreeView, you need to change TreeViewItem.Template and add CheckBox there. It won't help with GetTemplateChild, but is is more logical approach to build reusable TreeView with chechboxes.
Here is an example. Check/Uncheck operations can be handled by specail Command in CheckTreeView class:
public class CheckTreeView: TreeView
{
public CheckTreeView()
{
CheckCommand = new RelayCommand<object>(o => MessageBox.Show(o?.ToString()));
}
public ICommand CheckCommand
{
get { return (ICommand)GetValue(CheckCommandProperty); }
set { SetValue(CheckCommandProperty, value); }
}
public static readonly DependencyProperty CheckCommandProperty =
DependencyProperty.Register("CheckCommand", typeof(ICommand), typeof(CheckTreeView), new PropertyMetadata(null));
}
relevant part of TreeViewItem template (use Edit template feature in WPF designer to get full template):
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="19" Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ToggleButton x:Name="Expander" ClickMode="Press" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource ExpandCollapseToggleStyle}"/>
<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Column="1" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
<StackPanel Orientation="Horizontal">
<CheckBox Command="{Binding CheckCommand, RelativeSource={RelativeSource AncestorType={x:Type local:CheckTreeView}}}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
Margin="0,0,4,0"/>
<ContentPresenter x:Name="PART_Header" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</StackPanel>
</Border>
<ItemsPresenter x:Name="ItemsHost" Grid.ColumnSpan="2" Grid.Column="1" Grid.Row="1"/>
</Grid>
</ControlTemplate/>
</Setter.Value>
</Setter>
</Style>
Long story short. I have a dynamically created menu, viewModel for every item is below:
public class MenuItemViewModel
{
private readonly ICommand _command;
public MenuItemViewModel(Action<object> action)
{
_command = new CommandViewModel(action);
}
public string Header { get; set; }
public System.Windows.Controls.Primitives.PlacementMode Placement { get; set; }
public ObservableCollection<MenuItemViewModel> MenuItems { get; set; }
public ICommand Command
{
get
{
return _command;
}
}
}
Data context for whole window is another view model that has public ObservableCollection of those menuViewModel items"
public ObservableCollection<MenuItemViewModel> MenuItems { get; set; }
And after propgram startup I am adding new entries like this:
var currentDevice = (ControlableDeviceViewModel)sender;
var cmds = new ObservableCollection<MenuItemViewModel>();
foreach(var cmd in currentDevice.AvailableCommands)
{
cmds.Add(new MenuItemViewModel(cmd.Execute) { Header = cmd.CommandName,
Placement = System.Windows.Controls.Primitives.PlacementMode.Right});
}
MenuItems[0].MenuItems.Add(
new MenuItemViewModel((delegate {
RaiseAddNewDeviceEvent();
}))
{ Header = "Add new..." });
MenuItems[0].MenuItems.Add(
new MenuItemViewModel(null)
{
Header = currentDevice.Name + "->",
MenuItems = cmds
});
}
As you can see here I am adding placement only to cmds items because i want them to pop-up to right. Default behaviour is to pop up to bottom. And when i set Placement in my template in XAML it obviously set it for all items. But I want to set it only for specific item.
No matter in which way i try to bind for "Property" placement in model it is not working properly. It always have its default value "Bottom"
<Popup x:Name="PART_Popup" AllowsTransparency="true" Focusable="false" HorizontalOffset="2" IsOpen="{Binding IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}" PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}" Placement="{Binding RelativeSource={RelativeSource AncestorType={x:Type MenuItem},AncestorLevel='2'}, Path= Placement }" diag:PresentationTraceSources.TraceLevel="High" VerticalOffset="-1">
below is xaml part responsible for menu layout:
<Menu DockPanel.Dock="Top"
ItemsSource="{Binding MenuItems}"
Grid.Column="0"
Grid.ColumnSpan="5"
Grid.Row="0"
Foreground="White"
Background="Black"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
>
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type MenuItem}">
<Grid SnapsToDevicePixels="true">
<DockPanel>
<ContentPresenter x:Name="Icon" ContentSource="Icon" Margin="4,0,6,0" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center"/>
<Path x:Name="GlyphPanel" Fill="{TemplateBinding Foreground}" FlowDirection="LeftToRight" Margin="7,0,0,0" Visibility="Collapsed" VerticalAlignment="Center"/>
<ContentPresenter x:Name="content" ContentSource="Header" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</DockPanel>
<Popup x:Name="PART_Popup" AllowsTransparency="true" Focusable="false" HorizontalOffset="2" IsOpen="{Binding IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}" PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}" Placement="{Binding RelativeSource={RelativeSource AncestorType={x:Type MenuItem},AncestorLevel='1'}, Path= Placement }" diag:PresentationTraceSources.TraceLevel="High" VerticalOffset="-1">
<Border BorderThickness="2" BorderBrush="Black" Background="Black">
<ScrollViewer x:Name="SubMenuScrollViewer" CanContentScroll="true" Style="{DynamicResource {ComponentResourceKey ResourceId=MenuScrollViewer, TypeInTargetAssembly={x:Type FrameworkElement}}}">
<Grid RenderOptions.ClearTypeHint="Enabled">
<ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Cycle" Grid.IsSharedSizeScope="true" Margin="-1" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" KeyboardNavigation.TabNavigation="Cycle"/>
</Grid>
</ScrollViewer>
</Border>
</Popup>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="TextBlock.Foreground" Value="Gray" TargetName="content"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Menu.ItemContainerStyle>
<Menu.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type viewModels:MenuItemViewModel}" ItemsSource="{Binding Path=MenuItems}">
<TextBlock Text="{Binding Header}"
Padding="3"/>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
</Menu>
Many thanks for help me with solving this issue
Looks like the viewModel does not implement INotifyPropertyChanged interface and that's why the placement is always default.
Try this
public class MenuItemViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private readonly ICommand _command;
public MenuItemViewModel(Action<object> action)
{
_command = new CommandViewModel(action);
}
private string header;
public string Header
{
get
{
return header;
}
set
{
header = value;
NotifyPropertyChanged();
}
}
private System.Windows.Controls.Primitives.PlacementMode placement;
public System.Windows.Controls.Primitives.PlacementMode Placement
{
get
{
return placement;
}
set
{
placement = value;
NotifyPropertyChanged();
}
}
public ObservableCollection<MenuItemViewModel> MenuItems { get; set; }
public ICommand Command
{
get
{
return _command;
}
}
}
I came across something that maybe a bug in wpf listbox. Please see the code and then I explain what happens
Window
<Window >
<Grid>
<local:MultiSelectionComboBox Width="200"
MinHeight="25"
HorizontalAlignment="Center"
VerticalAlignment="Center"
ASLDisplayMemberPath="FirstName"
ASLSelectedItems="{Binding SelectedModels}"
ItemsSource="{Binding Models}" />
<ListBox HorizontalAlignment="Right"
VerticalAlignment="Top"
DisplayMemberPath="FirstName"
ItemsSource="{Binding SelectedModels}" />
</Grid>
</Window>
User control
<ComboBox>
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ComboBox}">
<Grid x:Name="MainGrid"
Width="Auto"
Height="Auto"
SnapsToDevicePixels="true">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="0" />
</Grid.ColumnDefinitions>
<Popup x:Name="PART_Popup"
Grid.ColumnSpan="2"
Margin="1"
AllowsTransparency="true"
IsOpen="{Binding Path=IsDropDownOpen,
RelativeSource={RelativeSource TemplatedParent}}"
Placement="Center"
PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}">
<Border x:Name="DropDownBorder"
MinWidth="{Binding Path=ActualWidth,
ElementName=MainGrid}"
MaxHeight="{TemplateBinding MaxDropDownHeight}">
<ScrollViewer CanContentScroll="true">
<StackPanel>
<CheckBox x:Name="checkBox"
Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ComboBox}, Path=CheckAllCommand}">select all</CheckBox>
<ListBox x:Name="lstBox"
here lies the problem without the line below I dont get to see the result on start up,
with the it selects the first item even if nothing is triggering it
IsSynchronizedWithCurrentItem="True"
ItemsSource="{TemplateBinding ItemsSource}"
KeyboardNavigation.DirectionalNavigation="Contained"
SelectionChanged="ListBoxOnSelectionChanged"
SelectionMode="Multiple"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</StackPanel>
</ScrollViewer>
</Border>
</Popup>
<ToggleButton Grid.ColumnSpan="2"
MinWidth="20"
HorizontalAlignment="Right"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
IsChecked="{Binding Path=IsDropDownOpen,
Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}"
Style="{DynamicResource ToggleButtonStyle1}" />
<ItemsControl x:Name="itemsControl"
Margin="4,2,0,2"
ItemsSource="{Binding Path=SelectedItems,
ElementName=lstBox}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="1"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Background="#383838"
CornerRadius="2"
Padding="5,2,3,2">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="Test"
Foreground="White"
Loaded="Test_OnLoaded"
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type ComboBox}},
Path=ASLDisplayMemberPathActualValue}" />
<Button Width="12"
Height="12"
Margin="5,0,0,0"
Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=ComboBox},
Path=UnselectCommand}"
CommandParameter="{Binding}">
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Ellipse x:Name="PART_ButtonBackground"
Fill="White"
Opacity="0" />
<TextBlock x:Name="PART_Text"
Margin="0,0,0,1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="10"
Foreground="White"
Text="X" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="PART_ButtonBackground" Property="Opacity" Value="0.8" />
<Setter TargetName="PART_Text" Property="Foreground" Value="Black" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ComboBox.Style>
</ComboBox>
code behind user control
public partial class MultiSelectionComboBox : ComboBox
{
#region fields, dependencies, command and constructor
private ListBox listBox;
private ItemsControl itemsControl;
private CheckBox checkBox;
public static readonly DependencyProperty ASLSelectedItemsProperty = DependencyProperty.Register("ASLSelectedItems", typeof(ObservableCollection<object>), typeof(MultiSelectionComboBox), new PropertyMetadata(null));
public static readonly DependencyProperty ASLDisplayMemberPathProperty = DependencyProperty.Register("ASLDisplayMemberPath", typeof(string), typeof(MultiSelectionComboBox), new PropertyMetadata("Description"));
public MultiSelectionComboBox()
{
InitializeComponent();
}
public DelegateCommand<object> UnselectCommand { get; private set; }
public DelegateCommand CheckAllCommand { get; private set; }
public ObservableCollection<object> ASLSelectedItems
{
get { return (ObservableCollection<object>)GetValue(ASLSelectedItemsProperty); }
set { SetValue(ASLSelectedItemsProperty, value); }
}
public string ASLDisplayMemberPath
{
get { return (string)GetValue(ASLDisplayMemberPathProperty); }
set { SetValue(ASLDisplayMemberPathProperty, value); }
}
#endregion
private void CheckAll()
{
if (checkBox.IsChecked == true)
{
listBox.SelectAll();
}
else
{
listBox.UnselectAll();
}
}
private void GetControls()
{
checkBox = Template.FindName("checkBox", this) as CheckBox;
listBox = Template.FindName("lstBox", this) as ListBox;
itemsControl = Template.FindName("itemsControl", this) as ItemsControl;
}
private bool bug = true;
private void ListBoxOnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
e.AddedItems.Cast<object>().Where(item => !ASLSelectedItems.Contains(item)).ForEach(p => ASLSelectedItems.Add(p));
e.RemovedItems.Cast<object>().Where(item => ASLSelectedItems.Contains(item)).ForEach(p => ASLSelectedItems.Remove(p));
checkBox.IsChecked = (listBox.ItemsSource as IList).Count == listBox.SelectedItems.Count;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
GetControls();
SetControls();
UnselectCommand = new DelegateCommand<object>(p => listBox?.SelectedItems.Remove(p));
CheckAllCommand = new DelegateCommand(CheckAll);
}
private void SetControls()
{
listBox.DisplayMemberPath = ASLDisplayMemberPath;
itemsControl.DisplayMemberPath = ASLDisplayMemberPath;
}
private void Test_OnLoaded(object sender, RoutedEventArgs e)
{
(sender as TextBlock)?.SetBinding(TextBlock.TextProperty, ASLDisplayMemberPath);
}
}
view model
public class MainWindowViewModel
{
public ObservableCollection<Model> Models { get; set; }
public ObservableCollection<object> SelectedModels { get; set; }
public MainWindowViewModel()
{
Models = new ObservableCollection<Model>()
{
new Model() {FirstName = "Amelia", Age = 0},
new Model() {FirstName = "Bruno", Age = 5},
new Model() {FirstName = "Colin", Age = 47},
new Model() {FirstName = "Daniel", Age = 32},
new Model() {FirstName = "Iza", Age = 28},
new Model() {FirstName = "James", Age = 23},
new Model() {FirstName = "Simon", Age = 23}
};
SelectedModels = new ObservableCollection<object> {Models.FirstOrDefault() };
}
}
Now the problem is that if inside the user control (where the listbox is) if I don’t set synchronize to true, then it won’t see the first item on start-up, if I do set it, then something forces an add to the collection. And then even if inside selected children I don’t set a value, it still sets the value of the first child.
This is actually multiselect combobox, got so far and this one simple thing stopped me for half of the day. And I can't find what is causing it.
Any help would be appreciated
This will help us on how to understand how listbox behaves when IsSynchronizedWithCurrentItem is set to true. Link
Given your requirement, this should be false.
Your LisBox.SelectionChanged event handler will be invoked every time you set the Values to ASLSelectedItems.
I think it will work by:
Remove your handler from your ListBox (lstBox).
Then modify your DP
public static readonly DependencyProperty ASLSelectedItemsProperty = DependencyProperty.Register("ASLSelectedItems", typeof(ObservableCollection<object>), typeof(MultiSelectionComboBox), new PropertyMetadata(null, OnSelectedItemsChanged));
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Add your logic from your handler
}
I have a grid view with a CollectionViewSource as it's items source.
I want to to bind the background property of each group container panel so that each group has its own background color.
how can this be achieved?
I'm trying to use binding in the <GroupStyle.ContainerStyle> of the gridview but can't get it to work.
Since the list will be grouped already, then applying a background on each GridViewItem will do the trick, depending on whether you want to define the backgound in each item as a property or use a converter to do that :
public class Data
{
public String Prop1 { get; set; }
public String Prop2 { get; set; }
public SolidColorBrush GroupeBrush { get; set; } //the groupe background color
}
And the xaml,
<Page.Resources>
<CollectionViewSource x:Name="DataCollection" IsSourceGrouped="true" />
</Page.Resources>
<Grid>
<GridView SelectionMode="None" ItemsSource="{Binding Source={StaticResource DataCollection}}" >
<GridView.ItemContainerStyle>
<Style TargetType="GridViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
<Setter Property="VerticalContentAlignment" Value="Stretch"></Setter>
</Style>
</GridView.ItemContainerStyle>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.ItemTemplate>
<DataTemplate>
<Grid Background="{Binding GroupeBrush}">
<TextBlock Text="{Binding Prop2}" />
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</Grid>
Or you could as well play around the GridView GroupStyle although you will need to find a way to bind the background from the Style Setter :
<GridView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Border Background="Black" HorizontalAlignment="Stretch">
<TextBlock Text='{Binding Key}' Foreground="White" Margin="5" Style="{StaticResource SubheaderTextBlockStyle}" />
</Border>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="GroupItem">
<Setter Property="MinWidth" Value="600"/>
<Setter Property="BorderBrush" Value="DarkGray"/>
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="Margin" Value="3,0"/>
<Setter Property="Background" Value="BurlyWood"/>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</GridView.GroupStyle>
Here the entire code behind in case any one wants to experiment more
public sealed partial class MainPage : Page
{
private ObservableCollection<Data> _datas = new ObservableCollection<Data>()
{
new Data()
{
Prop1 = "val1",
Prop2 = "val2",
GroupeBrush=new SolidColorBrush(Colors.Blue)
}, new Data()
{
Prop1 = "val1",
Prop2 = "val2",
GroupeBrush=new SolidColorBrush(Colors.Blue)
}, new Data()
{
Prop1 = "val1",
Prop2 = "val3",
GroupeBrush=new SolidColorBrush(Colors.Blue)
}, new Data()
{
Prop1 = "val2",
Prop2 = "val4",
GroupeBrush=new SolidColorBrush(Colors.Green)
}, new Data()
{
Prop1 = "val3",
Prop2 = "val5",
GroupeBrush=new SolidColorBrush(Colors.Red)
},
};
public ObservableCollection<Data> Datas
{
get
{
return _datas;
}
set
{
if (_datas == value)
{
return;
}
_datas = value;
}
}
public MainPage()
{
this.DataContext = this;
InitializeComponent();
DataCollection.Source = GetAllGrouped();
}
public IEnumerable<IGrouping<string, Data>> GetAllGrouped()
{
return Datas.GroupBy(x => x.Prop1);
}
}
public class Data
{
public String Prop1 { get; set; }
public String Prop2 { get; set; }
public SolidColorBrush GroupeBrush { get; set; } //the groupe background color
}
OK, here's what I've got. I had to modify the template of the Group Container Style:
<GroupStyle.ContainerStyle>
<Style TargetType="GroupItem">
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Margin" Value="10,0,0,0" />
<Setter Property="Padding" Value="20"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GroupItem">
<Border Background="{Binding Group, Converter={StaticResource ThemeColorConverter}}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ContentControl x:Name="HeaderContent"
Content="{TemplateBinding Content}"
ContentTransitions="{TemplateBinding ContentTransitions}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTemplateSelector="{TemplateBinding ContentTemplateSelector}"
Margin="{TemplateBinding Padding}"
TabIndex="0"
IsTabStop="False" />
<ItemsControl x:Name="ItemsControl"
Grid.Row="1"
ItemsSource="{Binding GroupItems}"
IsTabStop="False"
TabNavigation="Once"
TabIndex="1" >
<ItemsControl.ItemContainerTransitions>
<TransitionCollection>
<AddDeleteThemeTransition />
<ContentThemeTransition />
<ReorderThemeTransition />
<EntranceThemeTransition IsStaggeringEnabled="False" />
</TransitionCollection>
</ItemsControl.ItemContainerTransitions>
</ItemsControl>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
This line did the trick:
<Border Background="{Binding Group, Converter={StaticResource ColorConverter}}"/>
Binding to Group gives you access to the group's data source.
Suppose my Model class called person looks like below code:
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public Gender Gender { get; set; }
}
Gender class used in Person class looks like:
public class Gender
{
public int ID { get; set; }
public string Name { get; set; }
public string ImageData { get; set; }
}
Now in a view called EditView I am trying to display the information of currently selected Person:
<Page ...............>
<Page.DataContext>
<vm:EditViewModel />
</Page.DataContext>
<Grid DataContext="{Binding CurrentPerson}">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<TextBox Text="{Binding Name}" />
<ComboBox Grid.Row="1"
ItemsSource="{Binding DataContext.Genders, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Page}}}"
SelectedItem="{Binding Gender}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" Padding="6"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid x:Name="gd" TextElement.Foreground="Black">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Path Grid.Column="0" Data="{Binding ImageData}" Stretch="Uniform" Fill="Black" Width="24" Height="24" Margin="4" RenderTransformOrigin="0.5,0.5" />
<TextBlock Grid.Column="1" Text="{Binding Name}" Padding="6"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="ComboBoxItem.IsSelected" Value="True">
<Setter TargetName="gd" Property="Background" Value="#FF4CC4F6"></Setter>
<Setter TargetName="gd" Property="TextElement.Foreground" Value="White"></Setter>
</Trigger>
<Trigger Property="ComboBoxItem.IsMouseOver" Value="True">
<Setter TargetName="gd" Property="Background" Value="#FF84CDFA"></Setter>
<Setter TargetName="gd" Property="TextElement.Foreground" Value="White"></Setter>
</Trigger>
<Trigger Property="ComboBoxItem.IsHighlighted" Value="True">
<Setter TargetName="gd" Property="Background" Value="LightSkyBlue"></Setter>
<Setter TargetName="gd" Property="TextElement.Foreground" Value="Black"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</Grid>
</Page>
EditViewModel.cs code:
public class EditViewModel : ViewModelBase
{
public EditViewModel()
{
Genders = new ObservableCollection<Gender>(
from gender in XDocument.Load(DirectoryPaths.DataDirectory + #"Basic\Genders.xml")
.Element("Genders").Elements("Gender")
select new Gender
{
Id = Convert.ToInt32(gender.Attribute("Id").Value),
Name = gender.Element("Name").Value,
ImageData = gender.Element("ImageData").Value
}
);
}
private ObservableCollection<Gender> _genders;
public ObservableCollection<Gender> Genders
{
get
{
return _genders;
}
set
{
_genders = value;
NotifyPropertyChanged("Genders");
}
}
private Person _currentPerson;
public Person CurrentPerson
{
get
{
return _currentPerson;
}
set
{
_currentPerson = value;
NotifyPropertyChanged("CurrentPerson");
}
}
}
I have added the just relevent code for viewmodel. The property CurrentPerson has a person when EditView is shown. But in the ComboBox I do not get any selection by default. I am able to select values manually. But when the EditView loads I am not able to say the gender of the person as it does not get displayed as SelectedItem of the ComboBox.
You are running into a very common problem, two identical Gender objects are not equal to each other!
ComboBox sees the bound selected item, but doesn't see a matching item (actually, it doesn't see a matching reference) so it doesn't select anything.
To fix it, Gender needs to override Object.Equals (MSDN) and Object.GetHashCode (MSDN). According to this article implementing IEquatable will also work.