Handle Got_Focus & Lost_Focus for Popup inside of DataGrid - wpf

I have a DataGrid which has more options popup as a last column. When I click on a last cell of a row (more options popup icon) popup will get focused and show up, however instantly after that the row itself gets selected and will take focus away from popup which will cause the popup to close. In order to open it again I have to click twice on it.
It could be fixed by setting SelectionMode="None", but I want my rows to be selectable. Another workaround could be to try control focuses on events, but it also doesn't give an expected behavior:
PopupBox selectedPopupBox;
public CustomersView()
{
InitializeComponent();
}
private void PopupBox_GotFocus(object sender, RoutedEventArgs e)
{
var popupBox = sender as PopupBox;
selectedPopupBox = popupBox;
popupBox.IsPopupOpen = true;
}
private void CustomersDataGrid_SelectionChanged(object sender, GridSelectionChangedEventArgs e)
{
if (selectedPopupBox != null)
{
selectedPopupBox.Focus();
popupBox.IsPopupOpen = true;
}
}
The problem with the solution would be that whenever another row will be selected previous popup will be opened.
Similar problems I found: Syncfusion forum, Stackoverflow question
P.S. I don't want to open a popup when any other cell on a row is clicked, only when more options icon is clicked.
Attaching current behaviors to make it more clear:
Default behavior:
SelectionMode is set to None:
Workaround with code behind:

The solution to this problem was the answer from sf forum.
public partial class CustomersView : UserControl
{
public CustomersView()
{
InitializeComponent();
this.customersDataGrid.CellRenderers.Remove("TemplateExt");
this.customersDataGrid.CellRenderers.Add("TemplateExt", new GridCellTemplateExtension());
}
}
// Create Grid template column extension and specify it's cell type
public class MoreOptionsTemplateColumnExtension : GridTemplateColumn
{
public MoreOptionsTemplateColumnExtension()
{
SetCellType("TemplateExt");
}
}
public class GridCellTemplateExtension : GridCellTemplateRenderer
{
// This method will set the focus to the clicked cell
// instead of row itself
protected override void SetFocus(FrameworkElement uiElement, bool needToFocus)
{
if (!needToFocus)
DataGrid.Focus();
}
}
And you can you use this extension in your XAML like this:
<local:MoreOptionsTemplateColumnExtension Width="85"
AllowFocus="True"
AllowSorting="False">
<local:MoreOptionsTemplateColumnExtension.CellTemplate>
<DataTemplate>
<md:PopupBox Margin="5"
Padding="2,0,2,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="DarkSlateGray"
PopupMode="Click">
<!-- Your popup content -->
</md:PopupBox>
</DataTemplate>
</local:MoreOptionsTemplateColumnExtension.CellTemplate>
</local:MoreOptionsTemplateColumnExtension>

Related

Prevent jumping to next row when user hits Enter in RadGridView

I have a RadGridView from Telerik UI for WPF with an editable column. When user edits a cell in that column and hits Enter, the same cell in next row in the grid gets focus.
I want the cell that the user just edited to lose focus. How to do this MVVM-style?
You can change this behavior by writing a custom keyboard command provider. This provider overrides the default Enter press action. It commits the edits and deselects the cellby setting the SelectedItem to null.
public class CustomKeyboardCommandProvider : DefaultKeyboardCommandProvider
{
private readonly GridViewDataControl _grid;
public CustomKeyboardCommandProvider(GridViewDataControl grid)
: base(grid)
{
_grid = grid;
}
public override IEnumerable<ICommand> ProvideCommandsForKey(Key key)
{
var commands = base.ProvideCommandsForKey(key).ToList();
if (key != Key.Enter)
return commands;
commands.Clear();
commands.Add(RadGridViewCommands.CommitEdit);
_grid.SelectedItem = null;
return commands;
}
}
You have to assign the new provider. The easiest way is in code-behind, as it has a constructor parameter.
MyRadDataGridView.KeyboardCommandProvider = new CustomKeyboardCommandProvider(TestGridView);
Alternatively, create a TriggerAction<RadGridView> and attach it to the Loaded event of the grid view.
public class SetCustomKeyboardCommandProviderAction : TriggerAction<RadGridView>
{
protected override void Invoke(object parameter)
{
AssociatedObject.KeyboardCommandProvider = new CustomKeyboardCommandProvider(AssociatedObject);
}
}
<telerik:RadGridView>
<b:Interaction.Triggers>
<b:EventTrigger EventName="Loaded">
<local:SetCustomKeyboardCommandProviderAction/>
</b:EventTrigger>
</b:Interaction.Triggers>
<!-- ...other definitions. -->
</telerik:RadGridView>

WPF: why my Combobox SelectionChanged event is fired before my chart control created

So i have application and another sub form:
public partial class SubForm: MetroWindow...
And here is how i am open my Sub form from my main form:
SubForm subForm = new SubForm();
subForm.ShowDialog();
Inside my Sub form i have this chart control:
<telerik:RadCartesianChart
x:Name="chart" />
And Combobox:
<ComboBox
Name="cbInterfaces"
ItemsSource="{Binding Path=(my:MyClass.MachineInterfaces)}"
SelectedIndex="0"
SelectionChanged="cbInterfaces_SelectionChanged"/>
So i notice that after the Sub form oped right after InitializeComponent method, the code is go into my Combobox SelectionChanged event and my chart control is still null and not created yet.
So i cannot use it until use again my Combobox and change selection again (in this case my chart not null)
You could just return from the event handler immediately if the window or the RadCartesianChart haven't yet been initialized or loaded:
private void cbInterfaces_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!this.IsLoaded || chart == null || !chart.IsLoaded)
return; //do nothing
//your code...
}
Yes but the problem is that after this form created and opened i want to see my snuff immediately in my chat instead of change my combobox selection again ...
Set the SelectedIndex property programmatically after the call to the InitializeComponent() method then:
public partial class SubForm : Window
{
public SubForm()
{
InitializeComponent();
cbInterfaces.SelectedIndex = 0;
}
private void cbInterfaces_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//...
}
}
<ComboBox
Name="cbInterfaces"
ItemsSource="{Binding Path=(local:MyClass.MachineInterfaces)}"
SelectionChanged="cbInterfaces_SelectionChanged"/>

Turn off autoComplete in Combobox in wpf

I am using .NET framework 4.0 to build my application.
I have a combobox in which I want to turn off suggest-append mode of combobox. Instead I want suggest-only mode.
In many questions users ask for turning autoComplete feature off and everywhere I got the same answer. i.e. set IsTextSearchEnabled to False.
When IsTextSearchEnabled = True
When IsTextSearchEnabled = False
What I want is :
When User Presses Enter on the Combobox I want the Item to be appended to the textbox of the combobox.
Is this thing possible in WPF?
Like promised here is the demo. As you can see I did what I explained in my comments. I listened to text changed event.
Check it out:
<Grid>
<local:MyComboBox x:Name="comboBox" IsEditable="True"
VerticalAlignment="Center"
IsTextSearchEnabled="True">
<ComboBoxItem>hello</ComboBoxItem>
<ComboBoxItem>world</ComboBoxItem>
<ComboBoxItem>123</ComboBoxItem>
</local:MyComboBox>
</Grid>
public class MyComboBox : ComboBox
{
private string myValue;
private bool needsUpdate;
public override void OnApplyTemplate()
{
TextBox tbx = this.GetTemplateChild("PART_EditableTextBox") as TextBox;
tbx.PreviewKeyDown += (o, e) =>
{
this.needsUpdate = true;
};
tbx.TextChanged += (o, e) =>
{
if (needsUpdate)
{
myValue = tbx.Text;
this.needsUpdate = false;
}
else
{
tbx.Text = myValue;
}
};
base.OnApplyTemplate();
}
}

Passing origin of ContextMenu into WPF Command

Interesting problem related to firing commands from context menu items...
I want to fire a command to insert a row in my control, InsertRowCmd. This command needs to know where to insert the row.
I could use Mouse.GetPosition(), but that would get me the position of the mouse currently, which would be over the menu item. I want to get the origin of the context menu instead.
Does any one have any suggestions on how to pass the origin of the context menu as a parameter to the command?
Sample code:
<UserControl x:Name="MyControl">
<!--...-->
<ContextMenu x:Name="menu">
<MenuItem Header="Insert Row" Command="{x:Static customCommands:MyCommands.InsertRowCmd}" CommandParameter="?"/>
</ContextMenu>
</UserControl>
My current ideas are as follows:
-Use click handler instead so that I can find the origin in code. The problem is that I would then have to handle enabling/disabling.
-Handle click event and save the origin of the context menu. Pass this saved information into the command. I have verified that click events fire before the command is executed.
Any ideas?
EDIT:
I'm using Josh Smith's CommandSinkBinding to route the command handling into my ViewModel class. So the code that handles the command execution knows nothing about the view.
You'll need to use TranslatePoint to translate the top-left (0, 0) of the ContextMenu to a coordinate in the containing grid. You could do so by binding the CommandParameter to the ContextMenu and use a converter:
CommandParameter="{Binding IsOpen, ElementName=_menu, Converter={StaticResource PointConverter}}"
Another approach would be an attached behavior that automatically updates an attached readonly property of type Point whenever the ContextMenu is opened. Usage would look something like this:
<ContextMenu x:Name="_menu" local:TrackBehavior.TrackOpenLocation="True">
<MenuItem Command="..." CommandParameter="{Binding Path=(local:TrackBehavior.OpenLocation), ElementName=_menu}"/>
</ContextMenu>
So the TrackOpenLocation attached property does the work of attaching to the ContextMenu and updating a second attached property (OpenLocation) whenever the ContextMenu is opened. Then the MenuItem can just bind to OpenLocation to get the location at which the ContextMenu was last opened.
Following on from Kent's answer, I used his attached property suggestion and ended up with this (using Josh Smith's example for attached behaviors):
public static class TrackBehavior
{
public static readonly DependencyProperty TrackOpenLocationProperty = DependencyProperty.RegisterAttached("TrackOpenLocation", typeof(bool), typeof(TrackBehavior), new UIPropertyMetadata(false, OnTrackOpenLocationChanged));
public static bool GetTrackOpenLocation(ContextMenu item)
{
return (bool)item.GetValue(TrackOpenLocationProperty);
}
public static void SetTrackOpenLocation(ContextMenu item, bool value)
{
item.SetValue(TrackOpenLocationProperty, value);
}
public static readonly DependencyProperty OpenLocationProperty = DependencyProperty.RegisterAttached("OpenLocation", typeof(Point), typeof(TrackBehavior), new UIPropertyMetadata(new Point()));
public static Point GetOpenLocation(ContextMenu item)
{
return (Point)item.GetValue(OpenLocationProperty);
}
public static void SetOpenLocation(ContextMenu item, Point value)
{
item.SetValue(OpenLocationProperty, value);
}
static void OnTrackOpenLocationChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var menu = dependencyObject as ContextMenu;
if (menu == null)
{
return;
}
if (!(e.NewValue is bool))
{
return;
}
if ((bool)e.NewValue)
{
menu.Opened += menu_Opened;
}
else
{
menu.Opened -= menu_Opened;
}
}
static void menu_Opened(object sender, RoutedEventArgs e)
{
if (!ReferenceEquals(sender, e.OriginalSource))
{
return;
}
var menu = e.OriginalSource as ContextMenu;
if (menu != null)
{
SetOpenLocation(menu, Mouse.GetPosition(menu.PlacementTarget));
}
}
}
and then to use in the Xaml, you just need:
<ContextMenu x:Name="menu" Common:TrackBehavior.TrackOpenLocation="True">
<MenuItem Command="{Binding SomeCommand}" CommandParameter="{Binding Path=(Common:TrackBehavior.OpenLocation), ElementName=menu}" Header="Menu Text"/>
</ContextMenu>
However, I also needed to add:
NameScope.SetNameScope(menu, NameScope.GetNameScope(this));
to the constructor of my view, otherwise the binding for the CommandParameter couldn't lookup ElementName=menu.
In addition to Kent's answer, think about a "standard way". F.e. when a ListBox has a ContextMenu, you do not need menu's position, because the selected item is set before the menu popped up. So, if your control would have something that gets "selected" on the right click...

ComboBox with ItemTemplate that includes a button

So, lets say I have a ComboBox with a custom data template. One of the items in the data template is a button:
<ComboBox Width="150" ItemsSource="{Binding MyItems}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Button Content="ClickMe" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
The problem with this is that the button eats the click, and the item does not get selected if the button is selected. This means that the pull-down does not go away, and no item is selected.
I get WHY this is happening.
Is there a way to work around it? Possibly a way to process the button click (I am binding to a command) and tell it to continue up the chain so the combo box can also process the click?
Note: I am seeing my problem in Silverlight, but I am guessing that the exact same behavior can be seen with WPF.
OK, I got it figured out. It is a total hack, but it still lets me bind my command to the button and continue to have Combo-box behavior for selecting the item:
<ComboBox x:Name="MyCombo" Width="150" ItemsSource="{Binding MyItems}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Button Content="ClickMe" Click="Button_Click" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
And in the code behind:
private void Button_Click(object sender, RoutedEventArgs e)
{
MyCombo.SelectedItem = (sender as Button).DataContext;
MyCombo.IsDropDownOpen = false;
}
If I really wanted to, I could bind the SelectedItem and IsDropDownOpen to properties in my ViewModel but I decided against it to keep this behavior as a hack extension of the XAML, in an effort to keep my ViewModel clean.
Your best bet would probably be to set the SelectedItem in the button's command.
I found another possibility for the MVVM context. I used an derived class for ComboBox and if an item is adden which derives from ButtonBase I attach to the Click event to close the ComboBox.
This works for my project - but just, because the items itself are buttons, it would not work if they just contain buttons as a child element.
public class MyComboBox : ComboBox
{
public MyComboBox()
{
// use Loaded event to modify inital items.
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
if (Items != null)
{
foreach (var item in Items)
{
var button = item as ButtonBase;
if (button != null)
{
ModifyButtonItem(button);
}
}
}
}
protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
// Check added items. If an item is a button, modify the button.
if (e.NewItems != null)
{
foreach (var item in e.NewItems)
{
var button = item as ButtonBase;
if (button != null)
{
ModifyButtonItem(button);
}
}
}
}
private void ModifyButtonItem(ButtonBase button)
{
button.Click += (sender, args) => { IsDropDownOpen = false; };
}
}
I don't know if there is a way to do what you want. If you were to put a Button in a ListBox, for example, the same behavior occurs - clicking the Button does not cause its item in the ListBox to be selected. In fact, this is the case for any control in an ItemsControl that supports selection.
You might be able to do something with the Click event and mark it as not handled so that it continues up the visual tree, but even then I'm not sure if that would work or not.

Resources