This question already has answers here:
Get Grid Row# from ContextMenu Action
(2 answers)
Closed 8 years ago.
I got a Grid with controls such System.Windows.Controls.Image and Labels in each RowDefinition of my Grid. The problem is when I do the right click contextmenu it works and I can get the grid back but I cannot get the Row which the click occurred.
I do not know what UIElement is being clicked on as I want the user to be able to click on any element within the row boundaries.
By the way, I am using a Grid NOT a DataGrid!
Here is what I have already,
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Open Client CP" Background="#FF1C1C1C"/>
<MenuItem Header="Auto Mine" Background="#FF1C1C1C"/>
<MenuItem Header="Disconnect" Background="#FF1C1C1C"/>
<MenuItem Header="Uninstall" Background="#FF1C1C1C"/>
<MenuItem Header="Refresh" Background="#FF1C1C1C" Click="onRefreshMenuClick" CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Parent}"/>
</ContextMenu>
</Grid.ContextMenu>
private void onRefreshMenuClick(object sender, RoutedEventArgs e)
{
MenuItem mi = sender as MenuItem;
if (mi != null)
{
ContextMenu cm = mi.CommandParameter as ContextMenu;
if (cm != null)
{
Grid g = cm.PlacementTarget as Grid;
if (g != null)
{
// need something here like g.getrowof(cm.placementtarget)
if (debugWindow != null)
debugWindow.LogTextBox.AppendText("Requested refresh from "+ row);
}
}
}
}
You can find a solution in this post. Below I adapt that solution to your problem:
private void onRefreshMenuClick(object sender, RoutedEventArgs e)
{
MenuItem mi = sender as MenuItem;
if (mi != null)
{
ContextMenu cm = mi.CommandParameter as ContextMenu;
if (cm != null)
{
Grid g = cm.PlacementTarget as Grid;
if (g != null)
{
// need something here like g.getrowof(cm.placementtarget)
var point = Mouse.GetPosition(g);
int row = 0;
int col = 0;
double accumulatedHeight = 0.0;
double accumulatedWidth = 0.0;
// calc row mouse was over
foreach (var rowDefinition in g.RowDefinitions)
{
accumulatedHeight += rowDefinition.ActualHeight;
if (accumulatedHeight >= point.Y)
break;
row++;
}
// calc col mouse was over
foreach (var columnDefinition in g.ColumnDefinitions)
{
accumulatedWidth += columnDefinition.ActualWidth;
if (accumulatedWidth >= point.X)
break;
col++;
}
}
}
}
}
Related
I have a StackPanel with children. The StackPanel children are also StackPanel. Children StackPanel is added by dynamically at runtime. I have a context menu with delete header. When I click the delete menu the selected stack children will be deleted. I don't have any idea to remove the StackPanel children by using the context menu. Please, anyone, guide me to resolve this. My sample code is as below,
<StackPanel x:Name="mainPanel" Background="#F0F0F0">
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Click="ParentContextMenu_Click" Header="Add Stackpanel" />
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
Code behind
public partial class MainView : Window
{
ContextMenu contextMenu;
MenuItem menuItem;
public MainView()
{
InitializeComponent();
contextMenu = new ContextMenu();
menuItem = new MenuItem();
menuItem.Header = "Delete Panel";
menuItem.Click += ChildContextMenu_Click;
contextMenu.Items.Add(menuItem);
}
private void ChildContextMenu_Click(object sender, RoutedEventArgs e)
{
}
private void ParentContextMenu_Click(object sender, RoutedEventArgs e)
{
StackPanel stack = new StackPanel()
{
Name = "childStack"
Height = 100,
Width = 100,
Background = Brushes.White,
Margin = new Thickness(15, 15, 0, 10),
ContextMenu = contextMenu
};
mainPanel.Children.Add(stack);
}
}
I have tried like this also, but is not deleted.
mainPanel.Children.Remove((StackPanel)this.FindName("childStack"));
This should work:
private void ChildContextMenu_Click(object sender, RoutedEventArgs e)
{
MenuItem mi = sender as MenuItem;
if (mi != null)
{
ContextMenu cm = mi.Parent as ContextMenu;
if (cm != null)
{
StackPanel sp = cm.PlacementTarget as StackPanel;
if (sp != null)
{
Panel parentSp = sp.Parent as Panel;
if (parentSp != null)
parentSp.Children.Remove(sp);
}
}
}
}
I have a MainWindow. It has a stackpanel myStack and some other things.
In stackPanel, there is a usercontrol (TaskGrid(_TG)) added programmatically.
In this UserControl, there is a DataGrid(dgEmployee), which have 4 template columns. the last column contains a button.
I am trying to assign the button click event from the mainwindow constructor and handle the event here.
Here are the codes:
in MainWindow.xaml
<Grid Grid.Row="2">
<StackPanel Name="myStack"/>
</Grid>
in MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
_TG = new TaskGrid();
_TD = new _1.TaskDetails();
_TM = new _1.TaskMaster();
myStack.Children.Add(_TG);
_AUC = ActiveUserControl.Grid;
foreach (object child in myStack.Children)
{
string childname = "";
if (child is FrameworkElement)
{
childname = (child as FrameworkElement).Name;
if (childname == "TaskGrid")
{
Grid dg = ((Grid)((UserControl)child).Content);
foreach (var item in dg.Children)
{
DataGridColumn b = ((DataGrid)item).Columns[3] as DataGridColumn;
}
}
}
}
}
And in TaskGrid.xaml, the only template column is given here
<DataGridTemplateColumn Width="30">
<DataGridTemplateColumn.CellTemplate>
<ItemContainerTemplate>
<Button Name="btnMaster" Background="Transparent">
<Button.Template>
<ControlTemplate>
<Image Source="ArrowRight.png"/>
</ControlTemplate>
</Button.Template>
</Button>
</ItemContainerTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
I have to assign the click event like
button.click += new RoutedEvent(button_click);
And later use button_click event in the MainWindow.xaml.cs
Wait until the UserControl has been loaded. You could then get a reference to the DataGrid using the following helper method that searches for an element of a specific type recursively in the visual tree.
private static T GetChildOfType<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj == null)
return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
The same way you can get a reference to a specific cell:
public static DataGridCell GetCell(DataGrid dataGrid, DataGridRow rowContainer, int column)
{
if (rowContainer != null)
{
System.Windows.Controls.Primitives.DataGridCellsPresenter presenter =
GetChildOfType<System.Windows.Controls.Primitives.DataGridCellsPresenter>(rowContainer);
if (presenter != null)
return presenter.ItemContainerGenerator.ContainerFromIndex(column) as DataGridCell;
}
return null;
}
Please refer to the following blog post for more information about this: https://blog.magnusmontin.net/2013/11/08/how-to-programmatically-select-and-focus-a-row-or-cell-in-a-datagrid-in-wpf/
Below is a full example for you. Note that the DataGrid may contain several rows and some of the rows may have been virtualized away. You will find more information about this on the link above.
public MainWindow()
{
InitializeComponent();
_TG = new TaskGrid();
_TD = new _1.TaskDetails();
_TM = new _1.TaskMaster();
myStack.Children.Add(_TG);
_AUC = ActiveUserControl.Grid;
_TG.Loaded += (s, e) =>
{
DataGrid dataGrid = GetChildOfType<DataGrid>(_TG);
if (dataGrid != null)
{
foreach (var item in dataGrid.Items)
{
DataGridRow dgr = dataGrid.ItemContainerGenerator.ContainerFromItem(item) as DataGridRow;
if (dgr != null)
{
DataGridCell cell = GetCell(dataGrid, dgr, 3); //<-- column index
if (cell != null)
{
Button button = GetChildOfType<Button>(cell);
if (button != null)
{
button.Click += new RoutedEvent(button_click);
}
}
}
}
}
};
}
I have a ListView backed by an ObservableCollection. The user can add a new row, where in code I add a new object to the collection: array.Add(obj).
Now what I'd like to do is give focus to a TextBox in the new row. The problem is that I believe I need to wait until the UI is created, and I don't know of an event that will let me know when the new row is ready.
I've tried getting the new container and a reference to TextBox in ListView_SelectionChanged, but I was getting null return values on the new row.
I've tried using ListViewItem.Loaded, but this doesn't seem to be called for recycled rows.
I also tried ListViewItem.GotFocus, but this wasn't called after adding a new row in code.
If I knew when the controls on the ListViewItem were ready, I could then find the TextBox and set its focus.
Maybe I'm making this harder than it needs to be, but I'm not sure how to proceed.
I'm answering my own question. Below is what I came up with.
Xaml: (add two event handlers to Grid)
<DataTemplate x:Key="MyTemplate" x:DataType="model:Card">
<Grid GotFocus="ListViewGrid_GotFocus" DataContextChanged="ListViewGrid_DataContextChanged">
<StackPanel Orientation="Horizontal">
<TextBox Name="Text1" Text="{x:Bind Text1}" />
</StackPanel>
</Grid>
</DataTemplate>
Code:
MyListView.Items.VectorChanged += ListViewItems_VectorChanged; // in constructor
private void AddRow_Click(object sender, RoutedEventArgs e) {
card = ....
_newRowCard = card;
_array.Add(card);
}
private void ListViewItems_VectorChanged(IObservableVector<object> sender, IVectorChangedEventArgs #event) {
// If new row added, at this point we can safely select and scroll to new item
if (_newRowCard != null) {
MyListView.SelectedIndex = MyListView.Items.Count - 1; // select row
MyListView.ScrollIntoView(MyListView.Items[MyListView.Items.Count - 1]); // scroll to bottom; this will make sure new row is visible and that DataContextChanged is called
}
}
private void ListViewGrid_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args) {
// If new row added, at this point the UI is created and we can set focus to text box
if (_newRowCard != null) {
Grid grid = (Grid)sender;
Card card = (Card)grid.DataContext; // might be null
if (card == _newRowCard) {
TextBox textBox = FindControl<TextBox>(grid, typeof(TextBox), "Text1");
if (textBox != null) textBox.Focus(FocusState.Programmatic);
_newRowCard = null;
}
}
}
private void ListViewGrid_GotFocus(object sender, RoutedEventArgs e) {
// If user clicks on a control in the row, select entire row
MyListView.SelectedItem = (sender as Grid).DataContext;
}
public static T FindControl<T>(UIElement parent, Type targetType, string ControlName) where T : FrameworkElement {
if (parent == null) return null;
if (parent.GetType() == targetType && ((T)parent).Name == ControlName) return (T)parent;
int count = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++) {
UIElement child = (UIElement)VisualTreeHelper.GetChild(parent, i);
T result = FindControl<T>(child, targetType, ControlName);
if (result != null) return result;
}
return null;
}
I am using MahApps and MVVM Light. And I want to make DropDownButton opens on mouse enter. And hide it when mouse cursor leaves button and opened menu. For code simplification, I don't write code with EventToCommand. I just write code behind
XAML
<controls:DropDownButton x:Name="ddbVolume" Width="{Binding Path=ActualHeight, RelativeSource={RelativeSource Self}}"
ItemsSource="{Binding AudioControls}"
Icon="{DynamicResource appbar_settings}" BorderThickness="0"
ArrowVisibility="Collapsed"
Loaded="OnDropDownButtonLoaded" MouseEnter="OnDropDownButtonMouseEnter">
</controls:DropDownButton>
and .cs
private void OnDropDownButtonMouseEnter(object sender, MouseEventArgs e)
{
var dropDownButton = sender as DropDownButton;
if (dropDownButton != null && !dropDownButton.IsExpanded)
{
dropDownButton.IsExpanded = true;
}
}
private void OnDropDownButtonLoaded(object sender, RoutedEventArgs e)
{
var dropDownButton = sender as DropDownButton;
if (dropDownButton != null)
{
var template = dropDownButton.Template;
var menu = (ContextMenu)template.FindName("PART_Menu", dropDownButton);
menu.MouseLeave += (o, args) =>
{
if (dropDownButton.IsExpanded && !dropDownButton.IsMouseOver && !menu.IsMouseOver)
{
dropDownButton.IsExpanded = false;
}
};
menu.PreviewMouseMove += (o, args) =>
{
if (!dropDownButton.IsExpanded)
{
return;
}
var x = args.GetPosition(menu).X;
var y = args.GetPosition(menu).Y;
if (x < 0 | y < 0 | x > menu.ActualWidth | y > menu.ActualHeight)
{
menu.ReleaseMouseCapture();
}
};
}
else
{
this._logger.Debug($"Error loading DropDownButton");
}
But it does not work. The DropDownButton is only flicker on mouse over. Please, give me a proper solution, or any usefull advice to solve this problem.
If the menu is appearing at all then your opening logic is good, but then it disappears, meaning that your own code is somehow closing it.
Stick a breakpoint on the line where you set dropDownButton.IsExpanded = false, and you'll see that's it's being called I'm sure. You can then use the debugger to see why it's been invoked and fix the problem in your xaml that's causing the system to think that your mouse has left the menu.
Maybe, you should subscribe the MouseLeave Event. And you could fix your Actions.
I have made a solution. And it works as i expect. The root of the problem was, that DropDownButton uses ContextMenu to show list items. And this control is based on Popup, which uses his own window. And MouseLeave fired not at time, when mouse coursor was not over it, but when it's lost focus.
XAML
<controls:DropDownButton x:Name="ddbVolume" Width="{Binding Path=ActualHeight, RelativeSource={RelativeSource Self}}"
ItemsSource="{Binding AudioControls}"
Icon="{DynamicResource appbar_settings}" BorderThickness="0"
ArrowVisibility="Collapsed">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<command:EventToCommand Command="{Binding Source={x:Static commands:CommonCommands.DropDownButtonLoadedCommand}}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
<i:EventTrigger EventName="MouseEnter">
<command:EventToCommand Command="{Binding Source={x:Static commands:CommonCommands.DropDownButtonMouseEnterCommand}}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</controls:DropDownButton>
And a ViewModel code (I know it's not a VM, but it works the same way)
In static class I define commands that can be used anywhere in my application.
public static class CommonCommands
{
private static ICommand dropDownButtonLoadedCommand;
private static ICommand dropDownButtonMouseEnterCommand;
public static ICommand DropDownButtonLoadedCommand => dropDownButtonLoadedCommand;
public static ICommand DropDownButtonMouseEnterCommand => dropDownButtonMouseEnterCommand;
static CommonCommands()
{
dropDownButtonLoadedCommand = new RelayCommand<RoutedEventArgs>(DropDownButtonLoaded, x => true);
dropDownButtonMouseEnterCommand = new RelayCommand<MouseEventArgs>(DropDownButtonMouseEnter, x => true);
}
private static void DropDownButtonLoaded(RoutedEventArgs args)
{
var dropDownButton = args.Source as DropDownButton;
if (dropDownButton != null)
{
var template = dropDownButton.Template;
var menu = (ContextMenu)template.FindName("PART_Menu", dropDownButton);
var button = (Button)template.FindName("PART_Button", dropDownButton);
menu.MouseLeave += (o, e) =>
{
if (dropDownButton.IsExpanded && !dropDownButton.IsMouseOver && !menu.IsMouseOver)
{
dropDownButton.IsExpanded = false;
}
};
menu.PreviewMouseMove += (o, e) =>
{
if (!dropDownButton.IsExpanded || !menu.IsOpen)
{
return;
}
var x = e.GetPosition(menu).X;
var y = e.GetPosition(menu).Y;
if (x < 0 | y < -button.ActualHeight | x > menu.ActualWidth | y > menu.ActualHeight)
{
menu.ReleaseMouseCapture();
}
};
}
}
private static void DropDownButtonMouseEnter(MouseEventArgs args)
{
var dropDownButton = args.Source as DropDownButton;
if (dropDownButton != null && !dropDownButton.IsExpanded)
{
dropDownButton.IsExpanded = true;
}
}
}
I know there are some little defects. For example, "expression y < -button.ActualHeight" is not good at all. the proper way is to use button.IsMouseOver in MouseLeave event.
I have created the following Listbox.Itemtemplate:
<ListBox.ItemTemplate>
<DataTemplate>
<Image Name="ChannelImage" Source="{Binding ImageUrl}" Width="56" Height="56" Margin="0,0,28,0" Tap="ChannelImage_Tap" Opacity="0.5"/>
</DataTemplate>
</ListBox.ItemTemplate>
By default the image inside the ItemTemplate should have opacity 0.5, now I want to "Highlight" (set opacity to 1.0) the image when the user taps the image.
I made this with the following code (Tap="ChannelImage_Tap"):
private void ChannelImage_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
((Image)sender).Opacity = 1.0;
}
But I don't know how to set the image back to opacity 0.5 when the user taps another image.
Do the following code in ListBox selection_change event instead image_tap
//I assume your listbox selection changed is ListBoxImage_Selectionchange
//ListBoxImage is your ListBox name
// LastSelectedIndex define globally
int LastSelectedIndex =0;
private void ListBoxImage_Selectionchange(object sender, SelectionChangedEventArgs e)
{
if (ListBoxImage.SelectedIndex == -1)
return;
if(LastSelectedIndex>0)
{
ListBoxItem lastItem =this.ListImage.ItemContainerGenerator.ContainerFromIndex(LastSelectedIndex) as ListBoxItem;
Image lastImage = FindFirstElementInVisualTree<Image>(lastItem);
lastImage.Opacity = 0.5;
}
ListBoxItem selectedItem = this.ListImage.ItemContainerGenerator.ContainerFromIndex(ListImage.SelectedIndex) as ListBoxItem;
Image selectedImage = FindFirstElementInVisualTree<Image>(selectedItem);
selectedImage.Opacity = 1.0;
LastSelectedIndex = ListBoxImage.SelectedIndex;
ListImage.SelectedIndex = -1;
}
private T FindFirstElementInVisualTree<T>(DependencyObject parentElement) where T : DependencyObject
{
var count = VisualTreeHelper.GetChildrenCount(parentElement);
if (count == 0)
return null;
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(parentElement, i);
if (child != null && child is T)
{
return (T)child;
}
else
{
var result = FindFirstElementInVisualTree<T>(child);
if (result != null)
return result;
}
}
return null;
}