I am attempting to extend TabControl so that I can add and delete items, I have previously done this by adding a close command to my viewmodel that raises an event and a subscription in the parent viewmodel will remove the item from the collection.
I would like to make this approach more generic and am attempting to implement the ApplicationCommands.Delete command.
ExtendedTabControl.cs
public class ExtendedTabControl : TabControl
{
public static readonly DependencyProperty CanUserDeleteTabsProperty = DependencyProperty.Register("CanUserDeleteTabs", typeof(bool), typeof(ExtendedTabControl), new PropertyMetadata(true, OnCanUserDeleteTabsChanged, OnCoerceCanUserDeleteTabs));
public bool CanUserDeleteTabs
{
get { return (bool)GetValue(CanUserDeleteTabsProperty); }
set { SetValue(CanUserDeleteTabsProperty, value); }
}
public static RoutedUICommand DeleteCommand
{
get { return ApplicationCommands.Delete; }
}
private IEditableCollectionView EditableItems
{
get { return (IEditableCollectionView)Items; }
}
private bool ItemIsSelected
{
get
{
if (this.SelectedItem != CollectionView.NewItemPlaceholder)
return true;
return false;
}
}
private static void OnCanExecuteDelete(object sender, CanExecuteRoutedEventArgs e)
{
((WorkspacesTabControl)sender).OnCanExecuteDelete(e);
}
private static void OnCanUserDeleteTabsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// The Delete command needs to have CanExecute run.
CommandManager.InvalidateRequerySuggested();
}
private static object OnCoerceCanUserDeleteTabs(DependencyObject d, object baseValue)
{
return ((WorkspacesTabControl)d).OnCoerceCanUserAddOrDeleteTabs((bool)baseValue, false);
}
private static void OnExecutedDelete(object sender, ExecutedRoutedEventArgs e)
{
((WorkspacesTabControl)sender).OnExecutedDelete(e);
}
static ExtendedTabControl()
{
Type ownerType = typeof(ExtendedTabControl);
DefaultStyleKeyProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(typeof(ExtendedTabControl)));
CommandManager.RegisterClassCommandBinding(ownerType, new CommandBinding(DeleteCommand, new ExecutedRoutedEventHandler(OnExecutedDelete), new CanExecuteRoutedEventHandler(OnCanExecuteDelete)));
}
protected virtual void OnCanExecuteDelete(CanExecuteRoutedEventArgs e)
{
// User is allowed to delete and there is a selection.
e.CanExecute = CanUserDeleteTabs && ItemIsSelected;
e.Handled = true;
}
#endregion
protected virtual void OnExecutedDelete(ExecutedRoutedEventArgs e)
{
if (ItemIsSelected)
{
object currentItem = SelectedItem;
int indexToSelect = Items.IndexOf(currentItem) - 1;
if (currentItem != CollectionView.NewItemPlaceholder)
EditableItems.Remove(currentItem);
// This should focus the row and bring it into view.
SetCurrentValue(SelectedItemProperty, Items[indexToSelect]);
}
e.Handled = true;
}
private bool OnCoerceCanUserAddOrDeleteTabs(bool baseValue, bool canUserAddTabsProperty)
{
// Only when the base value is true do we need to validate
// that the user can actually add or delete rows.
if (baseValue)
{
if (!this.IsEnabled)
{
// Disabled TabControls cannot be modified.
return false;
}
else
{
if ((canUserAddTabsProperty && !this.EditableItems.CanAddNew) || (!canUserAddTabsProperty && !this.EditableItems.CanRemove))
{
// The collection view does not allow the add or delete action.
return false;
}
}
}
return baseValue;
}
}
Generic.xaml
<!-- This template explains how to render a tab item with a close button. -->
<DataTemplate x:Key="CloseableTabItemHeader">
<DockPanel MinWidth="120">
<Button DockPanel.Dock="Right" Command="ApplicationCommands.Delete" Content="X" Cursor="Hand" Focusable="False" FontSize="10" FontWeight="Bold" Height="16" Width="16" />
<TextBlock Padding="0,0,10,0" Text="{Binding DisplayName}" VerticalAlignment="Center" />
</DockPanel>
</DataTemplate>
<Style x:Key="{x:Type local:ExtendedTabControl}" BasedOn="{StaticResource {x:Type TabControl}}" TargetType="{x:Type local:ExtendedTabControl}">
<Setter Property="ItemTemplate" Value="{StaticResource CloseableTabItemHeader}" />
</Style>
This nearly works, I can choose an item and remove it by either hitting my close button or using the delete key. However if I hit the close button of an item that isn't selected, it still removes the selected item. The reason for this behavior is obvious, but I'm not sure how to access the correct object for removal? I also need to assign the indexToSelect found in OnExecutedDelete in a better fashion though I'm comfortable I'll find a solution for this.
ExecutedRoutedEventArgs has property Parameter. Try to set DataContext of TabItem as CommandParameter:
<DataTemplate x:Key="CloseableTabItemHeader">
<DockPanel MinWidth="120">
<Button DockPanel.Dock="Right" Command="ApplicationCommands.Delete" CommandParameter="{Binding .}" Content="X" Cursor="Hand" Focusable="False" FontSize="10" FontWeight="Bold" Height="16" Width="16" />
<TextBlock Padding="0,0,10,0" Text="{Binding DisplayName}" VerticalAlignment="Center" />
</DockPanel>
Then you can access DataContext in OnExecutedDelete:
protected virtual void OnExecutedDelete(ExecutedRoutedEventArgs e)
{
if (ItemIsSelected)
{
object currentItem = e.Parameter ?? SelectedItem;
int indexToSelect = Items.IndexOf(currentItem) - 1;
...
}
e.Handled = true;
}
I've just done something very similar in http://dragablz.net
Two check-ins for managing add and remove of tabs:
https://github.com/ButchersBoy/Dragablz/commit/47bbc302f5ffeaa8c234269ab4ff11bc80f7fa10
https://github.com/ButchersBoy/Dragablz/commit/c1dce0435683db83f163a77ccb9a19b3218b3ca7
Related
I have a ListView, and I would like to change behaviour, so SelectionChanged event would fire on MouseUp, instead of MouseDown.
The reason why I want to change behaviour, is because when I want to drag objects (on MouseMove), the selection is changed when I click (MouseDown) on the objects to move.
I found that piece of code, but this is working only for simple selection, and I would like to allow selecting several items.
public static class SelectorBehavior
{
#region bool ShouldSelectItemOnMouseUp
public static readonly DependencyProperty ShouldSelectItemOnMouseUpProperty =
DependencyProperty.RegisterAttached(
"ShouldSelectItemOnMouseUp", typeof(bool), typeof(SelectorBehavior),
new PropertyMetadata(default(bool), HandleShouldSelectItemOnMouseUpChange));
public static void SetShouldSelectItemOnMouseUp(DependencyObject element, bool value)
{
element.SetValue(ShouldSelectItemOnMouseUpProperty, value);
}
public static bool GetShouldSelectItemOnMouseUp(DependencyObject element)
{
return (bool)element.GetValue(ShouldSelectItemOnMouseUpProperty);
}
private static void HandleShouldSelectItemOnMouseUpChange(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Selector selector)
{
selector.PreviewMouseDown -= HandleSelectPreviewMouseDown;
selector.MouseUp -= HandleSelectMouseUp;
if (Equals(e.NewValue, true))
{
selector.PreviewMouseDown += HandleSelectPreviewMouseDown;
selector.MouseUp += HandleSelectMouseUp;
}
}
}
private static void HandleSelectMouseUp(object sender, MouseButtonEventArgs e)
{
var selector = (Selector)sender;
if (e.ChangedButton == MouseButton.Left && e.OriginalSource is Visual source)
{
var container = selector.ContainerFromElement(source);
if (container != null)
{
var index = selector.ItemContainerGenerator.IndexFromContainer(container);
if (index >= 0)
{
selector.SelectedIndex = index;
}
}
}
}
private static void HandleSelectPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
e.Handled = e.ChangedButton == MouseButton.Left;
}
#endregion
}
Here is my Xaml :
<ListView x:Name="ListViewContract" SelectedItem="{Binding SelectedContract}" ItemsSource="{Binding ListContractsView}" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="4" MouseDoubleClick="ListViewContract_DoubleClick" GridViewColumnHeader.Click ="GridViewHeaderClicked" SelectionChanged="ListViewContract_SelectionChanged" Visibility="{Binding Grid1Visible, Converter={StaticResource BoolToVisConverter}}"
MouseMove="ListViewContract_MouseMove" local:SelectorBehavior.ShouldSelectItemOnMouseUp="True">
<ListView.View>
<GridView AllowsColumnReorder="true" x:Name="GridViewContract">
<GridViewColumn DisplayMemberBinding="{Binding ID}" Header="ID" Width="{Binding WidthIDs, Mode=TwoWay}"/>
<GridViewColumn DisplayMemberBinding="{Binding Name}" Header="{x:Static p:Resources.Name}" Width="{Binding WidthColumnContractName, Mode=TwoWay}"/>
<GridViewColumn DisplayMemberBinding="{Binding Code}" Header="{x:Static p:Resources.Number}" Width="{Binding WidthColumnContractCode, Mode=TwoWay}"/>
<GridViewColumn DisplayMemberBinding="{Binding Comm}" Header="{x:Static p:Resources.Commentary}" Width="{Binding WidthColumnContractComm, Mode=TwoWay}"/>
<GridViewColumn DisplayMemberBinding="{Binding Revision}" Header="{x:Static p:Resources.Revision}" Width="{Binding WidthColumnContractRev, Mode=TwoWay}"/>
<GridViewColumn Header="{x:Static p:Resources.Advancement}" Width="{Binding WidthColumnContractAdv, Mode=TwoWay}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<Rectangle Name="AFF_Track" Height="12" Stroke="black" StrokeThickness="1" Fill="{Binding RectangleProgression}" Tag="{Binding ID}" MouseMove="mouseOverProgressionContractAss">
<Rectangle.ToolTip>
<ContentControl Template="{StaticResource ToolTipOperations}"/>
</Rectangle.ToolTip>
</Rectangle>
<Rectangle Name="AFF_Track2" Height="12" Stroke="black" StrokeThickness="1" Fill="{Binding RectangleProgressionWeight}" Tag="{Binding ID}" MouseMove="mouseOverProgressionContractAss">
<Rectangle.ToolTip>
<ContentControl Template="{StaticResource ToolTipOperations}"/>
</Rectangle.ToolTip>
</Rectangle>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="{x:Static p:Resources.Advancement}" Width="{Binding WidthColumnContractAdvRep, Mode=TwoWay}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<Rectangle Name="AFF_TrackRep" Height="12" Stroke="black" StrokeThickness="1" Fill="{Binding RectangleProgressionRep}" Tag="{Binding ID}" MouseMove="mouseOverProgressionContractRep">
<Rectangle.ToolTip>
<ContentControl Template="{StaticResource ToolTipOperations}"/>
</Rectangle.ToolTip>
</Rectangle>
<Rectangle Name="AFF_Track2Rep" Height="12" Stroke="black" StrokeThickness="1" Fill="{Binding RectangleProgressionRepWeight}" Tag="{Binding ID}" MouseMove="mouseOverProgressionContractRep">
<Rectangle.ToolTip>
<ContentControl Template="{StaticResource ToolTipOperations}"/>
</Rectangle.ToolTip>
</Rectangle>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding Manager.CompleteName}" Header="{x:Static p:Resources.ProjectManager}" Width="{Binding WidthColumnContractManager, Mode=TwoWay}"/>
<GridViewColumn DisplayMemberBinding="{Binding Client.Name}" Header="{x:Static p:Resources.Customer}" Width="{Binding WidthColumnContractCustomer, Mode=TwoWay}"/>
<GridViewColumn DisplayMemberBinding="{Binding Source}" Header="{x:Static p:Resources.Source}" Width="{Binding WidthColumnContractSource, Mode=TwoWay}"/>
</GridView>
</ListView.View>
</ListView>
You can modify your behavior to handle controls that extend MultiSelector (like the DataGrid) and ListBox individually. ListBox (and therefore ListView too) is not a MultiSelector but supports multi selection when setting the ListBox.SelectionMode property to something different than SelectionMode.Single (which is the default).
For every other simple Selector you would have to track the selected items manually. You would also have to intercept the Selector.Unselected event to prevent the Selector from unselecting the selected items when in a multi select mode - Selector only supports single item selection.
The following example shows how you can track selected items in case the attached Selector is not a ListBox or a MultiSelector. For this reason the behavior exposes a public readonly SelectedItems dependency property.
The example also shows how to observe the pressed keyboard keys in order to filter multi select user actions. This example filters Shift or Ctrl keys as gesture to trigger the multi select behavior: random multi select while CTRL is pressed and range select while Shift key is pressed. Otherwise the Selector will behave as usual (single select on release of the left mouse button).
SelectorService.cs
public class SelectorService : DependencyObject
{
#region IsSelectItemOnMouseUpEnabled attached property
public static readonly DependencyProperty IsSelectItemOnMouseUpEnabledProperty = DependencyProperty.RegisterAttached(
"IsSelectItemOnMouseUpEnabled",
typeof(bool),
typeof(SelectorService),
new PropertyMetadata(default(bool), OnIsSelectItemOnMouseUpEnabledChanged));
public static void SetIsSelectItemOnMouseUpEnabled(DependencyObject attachedElement, bool value) => attachedElement.SetValue(IsSelectItemOnMouseUpEnabledProperty, value);
public static bool GetIsSelectItemOnMouseUpEnabled(DependencyObject attachedElement) => (bool)attachedElement.GetValue(IsSelectItemOnMouseUpEnabledProperty);
#endregion IsSelectItemOnMouseUpEnabled attached property
#region SelectedItems attached property
public static IList GetSelectedItems(DependencyObject attachedElement) => (IList)attachedElement.GetValue(SelectedItemsProperty);
public static void SetSelectedItems(DependencyObject attachedElement, IList value) => attachedElement.SetValue(SelectedItemsPropertyKey, value);
private static readonly DependencyPropertyKey SelectedItemsPropertyKey = DependencyProperty.RegisterAttachedReadOnly(
"SelectedItems",
typeof(IList),
typeof(SelectorService),
new PropertyMetadata(default));
public static readonly DependencyProperty SelectedItemsProperty = SelectedItemsPropertyKey.DependencyProperty;
#endregion SelectedItems attached property
#region SelectionMode attached property (private)
private static SelectionMode GetOriginalSelectionModeBackup(DependencyObject attachedElement) => (SelectionMode)attachedElement.GetValue(OriginalSelectionModeBackupProperty);
private static void SetOriginalSelectionModeBackup(DependencyObject attachedElement, SelectionMode value) => attachedElement.SetValue(OriginalSelectionModeBackupProperty, value);
private static readonly DependencyProperty OriginalSelectionModeBackupProperty = DependencyProperty.RegisterAttached(
"OriginalSelectionModeBackup",
typeof(SelectionMode),
typeof(SelectorService),
new PropertyMetadata(default));
#endregion SelectionMode attached property
private static bool IsRandomMultiSelectEngaged
=> Keyboard.Modifiers is ModifierKeys.Control;
private static bool IsRangeMultiSelectEngaged
=> Keyboard.Modifiers is ModifierKeys.Shift;
private static Dictionary<Selector, bool> IsMultiSelectAppliedMap { get; } = new Dictionary<Selector, bool>();
private static int SelectedRangeStartIndex { get; set; } = -1;
private static int SelectedRangeEndIndex { get; set; } = -1;
private static void OnIsSelectItemOnMouseUpEnabledChanged(DependencyObject attachedElement, DependencyPropertyChangedEventArgs e)
{
if (attachedElement is not Selector selector)
{
return;
}
if ((bool)e.NewValue)
{
WeakEventManager<FrameworkElement, MouseButtonEventArgs>.AddHandler(selector, nameof(selector.PreviewMouseLeftButtonDown), OnPreviewLeftMouseButtonDown);
WeakEventManager<FrameworkElement, MouseButtonEventArgs>.AddHandler(selector, nameof(selector.PreviewMouseLeftButtonUp), OnPreviewLeftMouseButtonUp);
if (selector.IsLoaded)
{
InitializeAttachedElement(selector);
}
else
{
selector.Loaded += OnSelectorLoaded;
}
}
else
{
WeakEventManager<FrameworkElement, MouseButtonEventArgs>.RemoveHandler(selector, nameof(selector.PreviewMouseLeftButtonDown), OnPreviewLeftMouseButtonDown);
WeakEventManager<FrameworkElement, MouseButtonEventArgs>.RemoveHandler(selector, nameof(selector.PreviewMouseLeftButtonUp), OnPreviewLeftMouseButtonUp);
SetSelectedItems(selector, null);
IsMultiSelectAppliedMap.Remove(selector);
selector.Loaded -= OnSelectorLoaded;
if (selector is ListBox listBox)
{
listBox.SelectionMode = GetOriginalSelectionModeBackup(listBox);
}
}
}
private static void OnSelectorLoaded(object sender, RoutedEventArgs e)
{
var selector = sender as Selector;
selector.Loaded -= OnSelectorLoaded;
InitializeAttachedElement(selector);
}
private static void InitializeAttachedElement(Selector selector)
{
IList selectedItems = new List<object>();
if (selector is ListBox listBox)
{
ValidateListBoxSelectionMode(listBox);
selectedItems = listBox.SelectedItems;
}
else if (selector is MultiSelector multiSelector)
{
selectedItems = multiSelector.SelectedItems;
}
else if (selector.SelectedItem is not null)
{
selectedItems.Add(selector.SelectedItem);
}
SetSelectedItems(selector, selectedItems);
IsMultiSelectAppliedMap.Add(selector, false);
}
private static void OnUnselected(object? sender, RoutedEventArgs e)
{
var itemContainer = sender as DependencyObject;
Selector.SetIsSelected(itemContainer, true);
Selector.RemoveUnselectedHandler(itemContainer, OnUnselected);
}
private static void OnPreviewLeftMouseButtonUp(object sender, MouseButtonEventArgs e)
{
var selector = sender as Selector;
DependencyObject itemContainerToSelect = ItemsControl.ContainerFromElement(selector, e.OriginalSource as DependencyObject);
DependencyObject currentSelectedItemContainer = selector.ItemContainerGenerator.ContainerFromItem(selector.SelectedItem);
if (itemContainerToSelect is null)
{
return;
}
if (IsRandomMultiSelectEngaged)
{
MultiSelectItems(selector, itemContainerToSelect, currentSelectedItemContainer);
}
else if (IsRangeMultiSelectEngaged)
{
MultiSelectRangeOfItems(selector, itemContainerToSelect, currentSelectedItemContainer);
}
else
{
SingleSelectItem(selector, itemContainerToSelect, currentSelectedItemContainer);
}
IsMultiSelectAppliedMap[selector] = IsRandomMultiSelectEngaged || IsRangeMultiSelectEngaged;
}
private static void MultiSelectRangeOfItems(Selector selector, DependencyObject itemContainerToSelect, DependencyObject currentSelectedItemContainer)
{
int clickedContainerIndex = selector.ItemContainerGenerator.IndexFromContainer(itemContainerToSelect);
// In case there is not any preselected item. Otherwis SingleSlectItem() has already set the SelectedRangeStartIndex property.
if (SelectedRangeStartIndex == -1)
{
SelectedRangeStartIndex = clickedContainerIndex;
DependencyObject itemContainer = selector.ItemContainerGenerator.ContainerFromIndex(SelectedRangeStartIndex);
MultiSelectItems(selector, itemContainer, currentSelectedItemContainer);
return;
}
// Complete the range selection
else if (SelectedRangeEndIndex == -1)
{
bool isSelectionRangeFromTopToBotton = clickedContainerIndex > SelectedRangeStartIndex;
if (isSelectionRangeFromTopToBotton)
{
SelectedRangeEndIndex = clickedContainerIndex;
}
else
{
// Selection is from bottom to top, so we need to swap start and end index
// as they are used to initialize the for-loop.
SelectedRangeEndIndex = SelectedRangeStartIndex;
SelectedRangeStartIndex = clickedContainerIndex;
}
for (int itemIndex = SelectedRangeStartIndex; itemIndex <= SelectedRangeEndIndex; itemIndex++)
{
DependencyObject itemContainer = selector.ItemContainerGenerator.ContainerFromIndex(itemIndex);
bool isContainerUnselected = !Selector.GetIsSelected(itemContainer);
if (isContainerUnselected)
{
MultiSelectItems(selector, itemContainer, currentSelectedItemContainer);
}
}
// Only remember start index to append sequential ranges (more clicks while Shift key is pressed)
// and invalidate the end index.
SelectedRangeEndIndex = -1;
}
}
private static void MultiSelectItems(Selector? selector, DependencyObject itemContainerToSelect, DependencyObject currentSelectedItemContainer)
{
bool oldIsSelectedValue = Selector.GetIsSelected(itemContainerToSelect);
// Toggle the current state
bool newIsSelectedValue = oldIsSelectedValue ^= true;
if (selector is ListBox listBox)
{
// In case the mode was overriden externally, force it back
// but store the changed value to allow roll back when the behavior gets disabled.
ValidateListBoxSelectionMode(listBox);
}
// If the current Selector instance does not support native multi select
// we need to prevent the Selector from unselecting previous selected items.
// By setting unselected items back to selected we can enforce a visual multi select feedback.
if (selector is not MultiSelector and not ListBox)
{
if (newIsSelectedValue && currentSelectedItemContainer is not null)
{
Selector.AddUnselectedHandler(currentSelectedItemContainer, OnUnselected);
}
}
Selector.SetIsSelected(itemContainerToSelect, newIsSelectedValue);
(itemContainerToSelect as UIElement)?.Focus();
if (selector is not MultiSelector and not ListBox)
{
object item = selector.ItemContainerGenerator.ItemFromContainer(itemContainerToSelect);
IList selectedItems = GetSelectedItems(selector);
if (newIsSelectedValue)
{
selectedItems.Add(item);
}
else
{
selectedItems.Remove(item);
}
}
}
private static void SingleSelectItem(Selector? selector, DependencyObject itemContainerToSelect, DependencyObject currentSelectedItemContainer)
{
bool isPreviousSelectMultiSelect = IsMultiSelectAppliedMap[selector];
if (!isPreviousSelectMultiSelect)
{
// Unselect the currently selected
if (currentSelectedItemContainer is not null)
{
Selector.SetIsSelected(currentSelectedItemContainer, false);
}
}
// If the Selector has multiple selected items and an item was clicked without the modifier key pressed,
// then we need to switch back to single selection mode and only select the currently clicked item.
else
{
// Invalidate tracked multi select range
SelectedRangeStartIndex = -1;
SelectedRangeEndIndex = -1;
if (selector is ListBox listBox)
{
ValidateListBoxSelectionMode(listBox);
listBox.UnselectAll();
}
else if (selector is MultiSelector multiSelector)
{
multiSelector.UnselectAll();
}
else
{
IList selectedItems = GetSelectedItems(selector);
foreach (object item in selectedItems)
{
DependencyObject itemContainer = selector.ItemContainerGenerator.ContainerFromItem(item);
Selector.SetIsSelected(itemContainer, false);
}
selectedItems.Clear();
}
}
// Execute single selection
Selector.SetIsSelected(itemContainerToSelect, true);
(itemContainerToSelect as UIElement)?.Focus();
if (selector is not MultiSelector and not ListBox)
{
IList selectedItems = GetSelectedItems(selector);
selectedItems.Clear();
selectedItems.Add(selector.SelectedItem);
}
// Track index in case the next click enabled select range (press Shift while click)
int clickedContainerIndex = selector.ItemContainerGenerator.IndexFromContainer(itemContainerToSelect);
SelectedRangeStartIndex = clickedContainerIndex;
return;
}
private static void ValidateListBoxSelectionMode(ListBox listBox)
{
if (listBox.SelectionMode is not SelectionMode.Extended)
{
// In case the mode was overriden externally, force it back
// but store the changed value to allow roll back when the behavior gets disabled.
SetOriginalSelectionModeBackup(listBox, listBox.SelectionMode);
listBox.SelectionMode = SelectionMode.Extended;
}
}
private static void OnPreviewLeftMouseButtonDown(object sender, MouseButtonEventArgs e)
=> e.Handled = true;
}
In addition to #BionicCode 's code : the code doesn't take in charge DoubleClick. I could solve it adding that code : (BionicCode, if you could edit answer for future visitors, thanks).
-Add a static variable in the class :
public static DateTime lastClickDateTime = DateTime.Now;
Then at the very beginning of SingleSelectItem function :
TimeSpan interval = DateTime.Now - lastClickDateTime;
if(interval.TotalMilliseconds<500)
{
if(selector is ListBox listBox)
{
MouseButtonEventArgs doubleClickEvent = new MouseButtonEventArgs(Mouse.PrimaryDevice, (int)DateTime.Now.Ticks, MouseButton.Left);
doubleClickEvent.RoutedEvent = Control.MouseDoubleClickEvent;
doubleClickEvent.Source = selector;
selector.RaiseEvent(doubleClickEvent);
return;
}
}
lastClickDateTime = DateTime.Now;
Thanks again for your help :)
I'm creating a custom WPF control (CheckedListComboBox) that allows a user to select one or more options from a list. Essentially the user opens a dropdown-style control and checks off the items they would like to select. When the user checks or unchecks an option in the list, the main (non-popup) area of the control is updated to reflect the choices the user has made.
Here's an example of the control in action:
I am mostly satisfied with the state of the control, but there is one piece I would like to improve and I'm unsure of how to proceed.
The generation of the text that reflects the user's choice(s) currently relies on calling ToString on each of the selected items. This is fine in the example case above, as all of the objects I've passed to the control are strings. However, if I passed in a custom object that didn't override ToString, I would simply get the fully qualified class of the object (e.g. MyNamespace.MyObject).
What I would like to do is implement something akin to a WPF ComboBox's DisplayMemberPath property, where I can simply specify the property to display in the TextBox area of the control for each selected item.
I could just be lazy and go about this via Reflection, but I'm aware that this may not be the fastest (performance-wise) approach.
Here are the options I've considered so far:
Force all bound items to implement a specific interface - if all items passed to the control implemented an interface, such as ICheckableItem, I could then safely access the same property on each item to populate the TextBox. I'm shying away from this approach as I would like the control to be open about what sort of items it can accept.
Use an ItemsControl to display the text, instead of generating it in the code-behind - theoretically I could maintain a list of checked items privately within the control, bind that list to an ItemsControl within the control itself and then somehow bind to the property determined in DisplayMemberPath. I don't know if this is even possible, as I think it would have to do some sort of double-binding magic to work, and I don't think that is doable. There are also issues with getting the separator (in this case a comma) to appear if I follow this route.
The options I've listed above don't seem to work, and I can't think of any more possible approaches. Can anyone offer any other solutions?
Here's my code so far:
XAML
<UserControl
x:Class="MyNamespace.CheckedListComboBox"
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"
d:DesignHeight="30"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid x:Name="MainGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid x:Name="TextBoxHolderGrid">
<TextBox
x:Name="PART_TextBox"
MaxWidth="{Binding ActualWidth, ElementName=TextBoxHolderGrid}"
Focusable="False"
IsReadOnly="True"
TextWrapping="NoWrap" />
</Grid>
<ToggleButton
x:Name="PART_ToggleButton"
Grid.Column="1"
Margin="-1,0,0,0"
HorizontalAlignment="Right"
ClickMode="Press"
Focusable="False"
IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}"
IsTabStop="False">
<Path
x:Name="CollapsedArrow"
Margin="2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="M 0 0 L 4 4 L 8 0 Z">
<Path.Fill>
<SolidColorBrush Color="{x:Static SystemColors.ControlTextColor}" />
</Path.Fill>
</Path>
</ToggleButton>
<Popup
x:Name="PART_Popup"
Grid.ColumnSpan="2"
MinWidth="{Binding ActualWidth, ElementName=MainGrid}"
MaxHeight="{Binding MaxPopupHeight, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}"
Margin="0,-1,0,0"
IsOpen="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}"
Placement="Bottom"
StaysOpen="False">
<Border BorderThickness="1">
<Border.BorderBrush>
<SolidColorBrush Color="{x:Static SystemColors.ControlTextColor}" />
</Border.BorderBrush>
<ScrollViewer x:Name="PART_DropDownScrollViewer" BorderThickness="1">
<ItemsControl
ItemTemplate="{Binding ItemTemplate, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}"
ItemsSource="{Binding ItemsSource, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}"
KeyboardNavigation.DirectionalNavigation="Contained">
<ItemsControl.Background>
<SolidColorBrush Color="{x:Static SystemColors.ControlLightLightColor}" />
</ItemsControl.Background>
</ItemsControl>
</ScrollViewer>
</Border>
</Popup>
</Grid>
</UserControl>
Code behind
public partial class CheckedListComboBox : UserControl
{
private bool mouseIsOverPopup = false;
public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(CheckedListComboBox), new PropertyMetadata(null));
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable<CheckedListObject>), typeof(CheckedListComboBox), new PropertyMetadata(null, ItemsSourcePropertyChanged));
public static readonly DependencyProperty IsDropDownOpenProperty = DependencyProperty.Register("IsDropDownOpen", typeof(bool), typeof(CheckedListComboBox), new PropertyMetadata(false, IsDropDownOpenChanged));
public static readonly DependencyProperty MaxPopupHeightProperty = DependencyProperty.Register("MaxPopupHeight", typeof(double), typeof(CheckedListComboBox), new PropertyMetadata((double)200));
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
public IEnumerable<CheckedListObject> ItemsSource
{
get { return (IEnumerable<CheckedListObject>)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public bool IsDropDownOpen
{
get { return (bool)GetValue(IsDropDownOpenProperty); }
set { SetValue(IsDropDownOpenProperty, value); }
}
public double MaxPopupHeight
{
get { return (double)GetValue(MaxPopupHeightProperty); }
set { SetValue(MaxPopupHeightProperty, value); }
}
public CheckedListComboBox()
{
InitializeComponent();
}
private static void ItemsSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CheckedListComboBox checkedListComboBox)
{
if (e.OldValue != null && e.OldValue is IEnumerable<CheckedListObject> oldItems)
{
foreach (var item in oldItems)
{
item.PropertyChanged -= checkedListComboBox.SubItemPropertyChanged;
}
}
if (e.OldValue != null && e.OldValue is INotifyCollectionChanged oldCollection)
{
oldCollection.CollectionChanged -= checkedListComboBox.ItemsSourceCollectionChanged;
}
if (e.NewValue != null && e.NewValue is IEnumerable<CheckedListObject> newItems)
{
foreach (var item in newItems)
{
item.PropertyChanged += checkedListComboBox.SubItemPropertyChanged;
}
}
if (e.NewValue != null && e.NewValue is INotifyCollectionChanged newCollection)
{
newCollection.CollectionChanged += checkedListComboBox.ItemsSourceCollectionChanged;
}
checkedListComboBox.CalculateComboBoxText();
}
}
private void SubItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName.Equals("IsChecked", StringComparison.OrdinalIgnoreCase))
{
CalculateComboBoxText();
}
}
private void ItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// The bound ItemsSource collection has changed, so we want to unsubscribe any old items
// from the PropertyChanged event, and subscribe any new ones to this event.
if (e.OldItems != null)
{
foreach (var oldItem in e.OldItems)
{
if (oldItem is CheckedListObject item)
{
item.PropertyChanged -= SubItemPropertyChanged;
}
}
}
if (e.NewItems != null)
{
foreach (var newItem in e.NewItems)
{
if (newItem is CheckedListObject item)
{
item.PropertyChanged += SubItemPropertyChanged;
}
}
}
// We also want to re-calculate the text in the ComboBox, in case any checked items
// have been added or removed.
CalculateComboBoxText();
}
private void CalculateComboBoxText()
{
var checkedItems = ItemsSource?.Where(item => item.IsChecked);
if (checkedItems?.Any() ?? false)
{
PART_TextBox.Text = string.Join(", ", checkedItems.Select(i => i.Item?.ToString()));
}
else
{
PART_TextBox.Text = "";
}
}
private static void IsDropDownOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CheckedListComboBox box)
{
if (box.IsDropDownOpen)
{
Mouse.Capture(box, CaptureMode.SubTree);
}
else
{
if (Mouse.Captured?.Equals(box) ?? false)
{
Mouse.Capture(null);
}
}
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (IsDropDownOpen)
{
var textBoxPoint = e.GetPosition(PART_TextBox);
var popupPoint = e.GetPosition(PART_Popup.Child);
var mouseIsOverTextBox = !(textBoxPoint.X < 0 || textBoxPoint.X > PART_TextBox.ActualWidth || textBoxPoint.Y < 0 || textBoxPoint.Y > PART_TextBox.ActualHeight);
var mouseIsOverPopup = !(popupPoint.X < 0 || popupPoint.X > PART_Popup.Child.RenderSize.Width || popupPoint.Y < 0 || popupPoint.Y > PART_Popup.Child.RenderSize.Height);
if (mouseIsOverPopup && !mouseIsOverTextBox)
{
mouseIsOverPopup = true;
}
else
{
mouseIsOverPopup = false;
}
}
else
{
mouseIsOverPopup = false;
}
base.OnMouseMove(e);
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
if (IsDropDownOpen && !mouseIsOverPopup)
{
IsDropDownOpen = false;
}
base.OnMouseLeftButtonDown(e);
}
}
And here's the wrapper class for each of the items.
public class CheckedListObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isChecked;
private object item;
public CheckedListItem()
{ }
public CheckedListItem(object item, bool isChecked = false)
{
this.item = item;
this.isChecked = isChecked;
}
public object Item
{
get { return item; }
set
{
item = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Item"));
}
}
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
}
And finally, here's how I'm using the control:
<p:CheckedListComboBox Grid.Row="2" ItemsSource="{Binding Items}">
<p:CheckedListComboBox.ItemTemplate>
<DataTemplate>
<Grid Margin="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<CheckBox Margin="0,0,6,0" IsChecked="{Binding IsChecked}" />
<TextBlock Grid.Column="1" Text="{Binding Item}" />
</Grid>
</DataTemplate>
</p:CheckedListComboBox.ItemTemplate>
</p:CheckedListComboBox>
I'm working with Xamarin.Forms in a PCL project.
I have a page/screen where there are an ListView control. I have created a custom DataTemplate for ViewCell.
This ViewCell has different controls: some Labels, one Button and also a Entry.
<ListView x:Name="lvProducts" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout BackgroundColor="#FFFFFF" Orientation="Vertical">
<Grid Padding="5">
...
<Button Grid.Row="0" Grid.Column="1" Text="X"
CommandParameter="{Binding MarkReference}"
Clicked="DeleteItemClicked" />
...
<StackLayout Grid.Row="2" Grid.ColumnSpan="2" Orientation="Horizontal" >
<Label Text="Ref.: " FontSize="24" FontAttributes="Bold" TextColor="#000000" />
<Label Text="{Binding Reference}" FontSize="24" TextColor="#000000" />
</StackLayout>
...
<Entry Grid.Row="3" Grid.Column="1" Text="{Binding NumElements}"
Keyboard="Numeric" Placeholder="" FontSize="24"
HorizontalTextAlignment="Center" Focused="OnItemFocus"
Unfocused="OnItemUnfocus" />
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I want to achieve two things with this Entry control that I'm not able achieve:
First, when I add a new item, I would like that this new item has his Entry the focus, ready to start typing.
Second, when the user ends to write a value into the Entry, I would like to change the value of the behind. I would like know which Entry of ListView has modified. I tried to use the Unfocused event, but in the params of the method that launches only has a sender param that returns the Entry object, no reference about the model that has binded.
public void OnItemUnfocus(object sender, EventArgs e)
{
Entry entry = (Entry)sender;
//Here I would like to know the model object that's binded
//with this Entry / CellView item
}
How I can achieve these two points?
I'd like to suggest you to use behaviors:
public class FocusBehavior : Behavior<Entry>
{
private Entry _entry;
public static readonly BindableProperty IsFocusedProperty =
BindableProperty.Create("IsFocused",
typeof(bool),
typeof(FocusBehavior),
default(bool),
propertyChanged: OnIsFocusedChanged);
public int IsFocused
{
get { return (int)GetValue(IsFocusedProperty); }
set { SetValue(IsFocusedProperty, value); }
}
protected override void OnAttachedTo(Entry bindable)
{
base.OnAttachedTo(bindable);
_entry = bindable;
}
private static void OnIsFocusedChanged(BindableObject bindable, object oldValue, object newValue)
{
var behavior = bindable as FocusBehavior;
var isFocused = (bool)newValue;
if (isFocused)
{
behavior._entry.Focus();
}
}
}
<ListView x:Name="TasksListView"
ItemsSource={Binding Tasks}
RowHeight="200">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell x:Name="ViewCell">
<Grid x:Name="RootGrid"
Padding="10,10,10,0"
BindingContext="{Binding}">
<Entry>
<Entry.Behaviors>
<helpers:FocusBehavior IsFocused="{Binding BindingContext.IsFocused, Source={x:Reference RootGrid}}"/>
</Entry.Behaviors>
</Entry>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And my model:
public class TaskModel : INotifyPropertyChanged
{
private bool _isFocused;
public bool IsFocused
{
get { return _isFocused; }
set
{
_isFocused = value;
RaisePropertyChanged();
}
}
And in ViewModel, after adding new item, set it's IsFocused property to true.
The same thing with behavior you could use for TextChanged for Entry.
For my first question, I have found a way to solve it. I don't know if it is the best solution.
I have extended ListView to CustomListView, and I have added and a dictionary of cells:
private Dictionary<string, Cell> dicCells;
Also, I have overridden SetupContent and UnhookContent methods.
SetupContent fires when new cell has been added and one of his params gives me the new cell that I save into the dictionary. (MarkReference is my key value)
//When a new cell item has added, we save it into a dictionary
protected override void SetupContent(Cell content, int index)
{
base.SetupContent(content, index);
ViewCell vc = (ViewCell)content;
if (vc != null)
{
BasketProduct bp = (BasketProduct)vc.BindingContext;
if (bp != null)
{
this.dicCells.Add(bp.MarkReference, content);
}
}
}
UnhookContent fires when a cell has been removed. I remove the item that exists into my dictionary.
//When a new cell item has removed, we remove from the dictionary
protected override void UnhookContent(Cell content)
{
base.UnhookContent(content);
ViewCell vc = (ViewCell)content;
if (vc != null)
{
BasketProduct bp = (BasketProduct)vc.BindingContext;
if (bp != null)
{
this.dicCells.Remove(bp.MarkReference);
}
}
}
Then, I have created a function that retrieves a Entry (CustomEntry in my case) that contains the object (BasketProduct in my case).
//Retrieves a CustomEntry control that are into the collection and represents the BasketProduct that we have passed
public CustomEntry GetEntry(BasketProduct bp)
{
CustomEntry ce = null;
if (bp != null && this.dicCells.ContainsKey(bp.MarkReference))
{
ViewCell vc = (ViewCell)this.dicCells[bp.MarkReference];
if (vc != null)
{
ce = (CustomEntry)((Grid)((StackLayout)vc.View).Children[0]).Children[4];
}
}
return ce;
}
When I want to give the focus on a certain Entry, I call this method:
//Put the focus on the CustomEntry control that represents de BasketProduct that they have passed
public void SetSelected(BasketProduct bp, bool withDelay)
{
CustomEntry entry = null;
entry = GetEntry(bp);
if (entry != null)
{
if (withDelay)
{
FocusDelay(entry);
} else
{
entry.Focus();
}
}
}
If I call the SetSelected() method from ItemTapped method, works fine, but if I call the SetSelected() method after adding a item in then collection, the Entry doesn't get the focus. In this case, I have done a trick.
private async void FocusDelay(CustomEntry entry)
{
await Task.Delay(500);
entry.Focus();
}
About second question, as #markusian suggested, I have extended the Entry (CustomEntry) control and in the Unfocused event I have done this:
private void CustomEntry_Unfocused(object sender, FocusEventArgs e)
{
try
{
//If the user leaves the field empty, we set the last value
BasketProduct bp = (BasketProduct)BindingContext;
if (this.Text.Trim().Equals(string.Empty))
{
this.Text = bp.NumElements.ToString();
}
}
catch (FormatException ex) { }
}
I have a view that contains a list box with various items in button form and a checkbox for each of the button. XAML:
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<CheckBox x:Name="CheckFavorite"
Width="Auto"
Height="Auto"
AutomationProperties.AutomationId="AID_FavoritesCheck"
IsChecked="{Binding Path=IsChecked,
Mode=TwoWay}"
Visibility="{Binding IsFavoriteConfiguredAndInDA,
Converter={StaticResource boolToVisibility}}"
Checked="OnContentChanged"
Unchecked="OnContentChanged"/>
<Button Grid.Column="1"
Width="240"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
AutomationProperties.AutomationId="AID_BtnLaunchFavorite"
Command="{Binding Path=LaunchFavorite}"
Content="{Binding Path=ModuleDisplayName}"
Cursor="Hand"
FontSize="{StaticResource UxLevel_5}"
Padding="24 12 2 12"
ToolTip="{Binding Path=ModuleDisplayName}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
I have another button called "Launch". Its basic functionality is to launch the tasks as per the selection in the above listbox items so that multiple tasks can be launched.Button XAML:
<Button Name="btnLaunch"
Width="80"
Height="25"
HorizontalAlignment="Left"
AutomationProperties.AutomationId="AUI_BtnLaunchForFavorite"
Command="{Binding LaunchCommand}"
Style="{StaticResource LaunchButtonStyle}"
Visibility="{Binding IsRIAMode,
Converter={StaticResource boolToVisibilityInverter}}">
Issue:
I want to enable/disable this launch button if atleast one item is selected and disable otherwise.How can i achieve? Pls help with some code snippets.
UPDATE:
Here is the launch command:
public ICommand LaunchCommand { get; private set; }
LaunchCommand = new DelegateCommand<object>(OnLaunch);
internal void OnLaunch(object sender)
{
var nFavItemToLaunchCount = Favorites.Count(favItem => favItem.IsChecked);
if (!IsSessionLimitReached(nFavItemToLaunchCount))
{
foreach (FavoriteItemViewModel favoriteItem in Favorites)
{
if (favoriteItem.IsChecked)
{
favoriteItem.LaunchFavorite.Execute(sender);
}
}
}
}
UPDATE 2:
I made the changes as per Krishna's comment:
LaunchCommand = new DelegateCommand<object>(OnLaunch,ToggleLaunch);
private bool ToggleLaunch(object obj)
{
if (Favorites.Count(i => i.IsChecked)!=0) //Favorites is the itemsource
{
return true;
}
return false;
}
Still the launch button is shown disabled always even when item is selected/checked.
UPDATE 3
After Krishna's further comment, i changed the implementation of the property IsChecked and also implemented INotifyPropertyChanged in the viewmodel.Still no luck!
public class FavoriteItemViewModel:INotifyPropertyChanged
{
public bool IsChecked
{
get
{
return m_IsChecked;
}
set
{
m_IsChecked = value;
OnPropertyChanged("IsChecked");
}
}
/// <summary>
/// Handler for property change
/// </summary>
/// <param name="propertyName">Name of property</param>
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
There is another viewmodel "FavoriteContainerViewModel" that holds the collection of "FavoriteItemViewModel" and also implements INotifyPropertyChanged. This container view model is the place where i added the code mentioned in UPDATE 2
UPDATE 4:
Implementation of favorites:
public class FavoriteContainerViewModel : ViewModelBase, IModuleView
{
private readonly ObservableCollection<FavoriteItemViewModel> m_Favorites = new ObservableCollection<FavoriteItemViewModel>();
public ObservableCollection<FavoriteItemViewModel> Favorites
{
get { return m_Favorites; }
}
public FavoriteContainerViewModel()
{
LaunchCommand = new DelegateCommand<object>(OnLaunch, ToggleLaunch);
OnLoad();
}
private bool ToggleLaunch(object obj)
{
if (Favorites.Count(i => i.IsChecked) != 0)
{
return true;
}
return false;
//return true;
}
}
Note:ViewModelBase implements INotifyPropertyChanged
Update 5:
Problem now resolved using event based model. Implemented following:
FavoriteViewModel.cs
public event EventHandler ItemChecked;
public bool IsChecked
{
get
{
return m_IsChecked;
}
set
{
m_IsChecked = value;
if (ItemChecked != null)
{
ItemChecked(this, new EventArgs());
}
}
}
FavoriteItemContainerViewModel.cs
private void SubscribeFavoriteItemEvents(FavoriteItemViewModel favorite)
{
favorite.ItemChecked += ToggleLaunchButton;
}
private bool m_IsLaunchEnabled;
public bool IsLaunchEnabled
{
get
{
return m_IsLaunchEnabled;
}
set
{
m_IsLaunchEnabled = value;
OnPropertyChanged("IsLaunchEnabled");
}
}
Property IsLaunchEnabled is binded to the button to enable/disable.
You can enable/disable the Button based on the CheckBox.IsChecked property:
<Button Name="btnLaunch"
Width="80"
Height="25"
HorizontalAlignment="Left"
AutomationProperties.AutomationId="AUI_BtnLaunchForFavorite"
Command="{Binding LaunchCommand}"
Style="{StaticResource LaunchButtonStyle}"
Visibility="{Binding IsRIAMode,
Converter={StaticResource boolToVisibilityInverter}}"
IsEnabled="{Binding ElementName=CheckFavorite, Path=IsChecked}">
I am guessing the launch button is outside the listbox and user clicks it once the checkboxes are checked.
In that case, you need to add a condition in the 'CanExecute' part of your ICommand (LaunchCommand)
Lets assume your ICommand implementation in the constructor is something like
LaunchCommand = new RelayCommand(launchMethod);
private void launchMethod(object obj)
{
//do some work here
}
Now add canExecute part to your command by changing the initialisation
LaunchCommand = new DelegateCommand<object>(OnLaunch,checkCheckboxes);
and add this method
private bool checkCheckboxes(object obj)
{
//YourList is your itemsource
return YourList.Where(i=>i.IsChecked).Any();
}
Just change the above method to suit your needs
Update change your IsChecked property to below
private bool isChecked;
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
OnPropertyChanged("IsChecked");
}
}
if (checkbox1.Checked && !checkbox2.Checked)
{
button1.Enable = true
}
else if (!checkbox1.Checked && checkbox2.Checked)
{
button1.Enable = false
}
I'm creating a Key/Value pair editor and would like for the value to have a custom template based on the data type.
<TextBox x:Uid="txtKey" x:Name="txtKey" Grid.Column="1" Grid.Row="0" Text="{Binding ElementName=This, Path=KeyValuePair.Key}" VerticalAlignment="Top"/>
<ContentControl Grid.Column="1" Grid.Row="1"
x:Uid="ContentControl1" x:Name="ContentControl1"
Content="{Binding ElementName=This, Path=KeyValuePair.Value}"
LostFocus="ContentControl1_LostFocus"
DataContextChanged="ContentControl1_DataContextChanged" />
The codebehind for the host class looks like this:
public partial class KeyValueControl : ControlBase
{
private System.Collections.DictionaryEntry _dictionaryEntry;
private KeyValuePairObjectObject _KeyValuePair = new KeyValuePairObjectObject();
private DataTemplate _editorDataTemplate;
private Caelum.Libraries.Ui.Editors.Resources resources = new Editors.Resources();
public DataTemplate EditorDataTemplate
{
get { return _editorDataTemplate; }
set { _editorDataTemplate = value; SendPropertyChanged("EditorDataTemplate"); }
}
public KeyValuePairObjectObject KeyValuePair
{
get { return _KeyValuePair; }
set { _KeyValuePair = value; SendPropertyChanged("KeyValuePair"); }
}
public KeyValueControl()
{
InitializeComponent();
this.DataUpdated += new DataUpdatedHander(KeyValueControl_DataUpdated);
DataContextChanged += new DependencyPropertyChangedEventHandler(KeyValueControl_DataContextChanged);
}
void KeyValueControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
}
public override void Save()
{
base.Save();
}
void KeyValueControl_DataUpdated(object sender, object data)
{
if (Data != null)
{
_dictionaryEntry = (System.Collections.DictionaryEntry)Data;
KeyValuePair.Key = _dictionaryEntry.Key;
KeyValuePair.Value = _dictionaryEntry.Value;
if (KeyValuePair.Value != null)
{
EditorDataTemplate = resources.GetDataTemplate(_dictionaryEntry.Value.GetType());
ContentControl1.ContentTemplate = EditorDataTemplate;
}
}
}
}
DataTemplates are chosen via the resources class:
public DataTemplate GetDataTemplate(Type type)
{
if (type == typeof(string))
{
return TextInlineEditorTemplate;
}
if (type == typeof(bool))
{
return BooleanInlineEditorTemplate;
}
return null;
}
The DataTemplate that is displayed for a string is:
<DataTemplate x:Uid="TextInlineEditorTemplate" x:Key="TextInlineEditorTemplate" >
<Grid>
<TextBox x:Uid="txtTextIET1" x:Name="txtTextIET1" Width="300" Text="{Binding Path=DataContext, Mode=TwoWay, RelativeSource={RelativeSource Self}, BindsDirectlyToSource=True, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</DataTemplate>
The data binds OK to the key TextBox (txtKey) and DataTemplate TextBox (txtTextIET1), but changing the value on txtTextIET1 will not trigger the setter on the KeyValuePair property. I've not been able to find any examples of this scenario, so any help would be appreciated.
Didn't this work for you
<DataTemplate x:Uid="TextInlineEditorTemplate" x:Key="TextInlineEditorTemplate" >
<Grid>
<TextBox x:Uid="txtTextIET1" x:Name="txtTextIET1" Width="300" Text="{Binding}" />
</Grid>
</DataTemplate>