WPF ComboBox DropDown Placement - wpf

I have a ContentControl comprised from left to right of a Button, partition and a ComboBox. I want the ComboBox dropdown to line up with the left side of the control as opposed to the left side of the combobox. Can't seem to find docs on Relative placement, etc. Anyone dealt with this? TIA

I've done something similar before - I ended up deriving from ComboBox, getting the popup part of the control and using the CustomPopupPlacementCallback to position it. Something like this...
class MyComboBox : ComboBox
{
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var popup = (Popup)Template.FindName("PART_Popup", this);
popup.Placement = PlacementMode.Custom;
popup.CustomPopupPlacementCallback = placePopup;
}
private CustomPopupPlacement[] placePopup(Size popupSize, Size targetSize, Point offset)
{
var placements = new[] { new CustomPopupPlacement() };
placements[0].Point = // position the drop-down here!
return placements;
}
}

Related

Handle Got_Focus & Lost_Focus for Popup inside of DataGrid

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>

wpf get screen position of selected item in listview

In the mouseup event for a listview, how do I get the screen position of a selected item? I can get the screen position of the listview itself (.pointtoscreen) but can't find a way to determine the screen position of a selected item.
I've reviewed other SO articles but didn't find anything specific to items in the listview.
You can handle the ListBox.SelectionChanged event (or Selector.Selected). Then get the container of the selected item to calculate its coordinates:
partial class MainWindow : Window
{
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listBox = sender as ListBox;
var selectedItemContainer = listBox.ItemContainerGenerator.ContainerFromItem(listBox.SelectedItem) as UIElement;
var pointOnSelectedItem = new Point(); // top-left corner
var pointOnScreen = selectedItemContainer.PointToScreen(pointOnSelectedItem);
var pointRelativeToWindow = selectedItemContainer.TranslatePoint(pointOnSelectedItem, this);
}
}

Focus on DataGridCell for SelectedItem when DataGrid Receives Keyboard Focus

I have a DataGrid where the SelectedItem is bound to a VM Selected property. I have a search control that will do a find and the SelectedItem of the DataGrid changes (and scrolls into view). WPF 4.0 and DataGrid SelectionUnit="FullRow".
My problem is with the focus. The DataGrid receives focus (via attached property / binding) but you can't use the Up, Down, Page Up, Page Down keys to change rows (SelectedItem). If I tab again, the first cell of the first row displayed is selected which changes the SelectedItem.
Bottom line, how can I give keyboard focus to the DataGridCell for the SelectedItem when the DataGrid receives focus?
There are so many DataGrid / Focus questions and tried a few things already. Thanks for your help.
You need to give the newly selected row logical focus. After selecting the new item try replacing your SetFocus call with this:
var selectedRow = (DataGridRow)dataGrid1.ItemContainerGenerator.ContainerFromIndex(dataGrid1.SelectedIndex);
FocusManager.SetIsFocusScope(selectedRow, true);
FocusManager.SetFocusedElement(selectedRow, selectedRow);
The FocusManager solution didn't work for me for some reason. Also I required a more general apporach. So here is, what I came up with:
using System.Windows.Controls;
public static void RestoreFocus(this DataGrid dataGrid,
int column = 0, bool scrollIntoView = false)
{
if (dataGrid.IsKeyboardFocusWithin && dataGrid.SelectedItem != null)
{
// make sure everything is up to date
dataGrid.UpdateLayout();
if (scrollIntoView)
{
dataGrid.ScrollIntoView(dataGrid.SelectedItem);
}
var cellcontent = dataGrid.Columns[column].GetCellContent(dataGrid.SelectedItem);
var cell = cellcontent?.Parent as DataGridCell;
if (cell != null)
{
cell.Focus();
}
}
}
And call it like this:
MyDataGrid.IsKeyboardFocusWithinChanged += (sender, e) =>
{
if ((bool)e.NewValue == true)
{
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Loaded, new Action(() =>
{
MyDataGrid.RestoreFocus(scrollIntoView: true);
}));
}
};
This PowerShell snippet worked for me:
$dataGrid = ...
$dataGrid.add_GotKeyboardFocus({
param($Sender,$EventArgs)
if ($EventArgs.OldFocus -isnot [System.Windows.Controls.DataGridCell) {
$row = $dataGrid.ItemContainerGenerator.ContainerFromIndex($dataGrid.SelectedIndex)
$row.MoveFocus((New-Object System.Windows.Input.TraversalRequest("Next")))
}
})

WPF DataGrid - maintain scroll position after refresh

Anyone knows how to maintain the vertical scroll position after the Refresh command?
My Datagrid receives binding from a List of objects.
Many thanks, and sorry for my english.
2 posible solutions :
1) Try to update the list instead of replacing it. it means adding new items and removing items you don't need.
2) you can save the state of the scroller before refreshing and then scroll programmly to the selected item.
WPF Toolkit: how to scroll datagrid to show selected item from code behind?
I had the same problem, here is what I did:
Find the ScrollViewer of your Datagrid/ListBox when it is leaded and add a ScrollChanged event:
var scrollViewer = FindScrollViewer(ListBoxOrders);
if (scrollViewer != null)
{
scrollViewer.ScrollChanged += scrollViewer_ScrollChanged;
}
//Here is the function to find the ScrollViewer:
private ScrollViewer FindScrollViewer(DependencyObject d)
{
if (d is ScrollViewer)
return d as ScrollViewer;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(d); i++)
{
var sw = FindScrollViewer(VisualTreeHelper.GetChild(d, i));
if (sw != null) return sw;
}
return null;
}
On scroll changed, store the vertical offset:
private double _verticalOffset;
private void scrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
var sv = (ScrollViewer)sender;
_verticalOffset = sv.VerticalOffset;
}
After refresh, scroll to the previous position:
scrollViewer?.ScrollToVerticalOffset(_verticalOffset);

Allowing item selection indicator in ListBox to overlay all items in Silverlight

I have a ListBox that uses a WrapPanel for its ItemsPanel, a custom ItemTemplate, and a custom ItemContainerStyle. The ItemContainerStyle's template contains a selection box that shows up when an item is selected. The graphics designer would like this selection box to overlap sibling items in the ListBox like it's an overlay.
The first thing I tried was setting the Canvas.ZIndex property of the ItemContainer in the Selected state. That did not seem to have an effect. Then I read that list items might be wrapped inside of a ContentPresenter, so I created an attached property that changes the ZIndex of an item's parent, but then I found out Silverlight storyboards don't let you animate custom attached properties.
Does anyone know of a technique we can use to achieve the effect we desire?
I found a solution. Basically, I created an attached property that sets up an event handler on any Selector (including ListBox) for when its selection changes. When it changes, the code iterates through all of the item containers, adjusting the Canvas.ZIndex based on whether the container represents the selected item:
public static readonly DependencyProperty SetZIndexOnSelectionProperty = DependencyProperty.RegisterAttached(
"SetZIndexOnSelection", typeof(bool), typeof(FrameworkUtils),
new PropertyMetadata(zIndexSettingChanged));
public static bool GetSetZIndexOnSelection(DependencyObject obj)
{
return (bool)obj.GetValue(SetZIndexOnSelectionProperty);
}
public static void SetSetZIndexOnSelection(
DependencyObject obj, bool value)
{
obj.SetValue(SetZIndexOnSelectionProperty, value);
}
private static void zIndexSettingChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is Selector)
{
var selector = obj as Selector;
selector.SelectionChanged += (s, e) =>
{
if (selector.SelectedItem != null)
{
foreach (var pair in selector.GetItemsAndContainers())
{
pair.Value.SetValue(
Canvas.ZIndexProperty,
(pair.Key == selector.SelectedItem) ? 1 : 0);
}
}
};
}
}

Resources