c# - how to handle with null selectedItem? - c#

i have an issue with selectedItem of a listbox. When I select an item of the listbox, a popup would be displayed where you click the add button to select an image (it contains a value of selectedItem) which is working fine. But after clicking the add button to select the image, then you realise the image is wrong, so you click the add button again to select another image, it started problem because selectedItem is null. How to handle it? How to stay the value of selectedItem? Your given code much appreciated.
if (lstDinner.SelectedItem != null)
{
output = _imageInserter.InsertImage(imageName, lstDinner.SelectedItem.ToString());
PopupToysImage.IsOpen = true;
strDinner.DinnersDetails = lstDinner.SelectedItem.ToString()
}
else
{
// strDinner.DinnersDetails = null that cause a problem.
output = _imageInserter.InsertImage(imageName, strDinner.DinnersDetails);
PopupDinnerImage.IsOpen = true;
}
UPDATE HERE:
WPF:
<ListBox Style="{DynamicResource ListBoxStyle1}" DisplayMemberPath="Dinner" BorderBrush="#FFF0F0F0" x:Name="lstDinner" FontSize="20" HorizontalAlignment="Left" Margin="0,110,0,72.667" Width="436" SelectionMode="Extended" PreviewMouseLeftButtonDown="MouseDownHandler" ScrollViewer.CanContentScroll="True" UseLayoutRounding="False" KeyDown="lstDinner_KeyDown" MouseDoubleClick="lstDinner_MouseDoubleClick" >
events in C#:
private void MouseDownHandler(object sender, MouseButtonEventArgs e)
{
var parent = (ListBox)sender;
_dragSource = parent;
var data = GetObjectDataFromPoint(parent, e.GetPosition(parent));
if (e.ChangedButton == MouseButton.Left && e.ClickCount == 1)
{
if (data != null)
DragDrop.DoDragDrop(parent, data, DragDropEffects.Move);
}
}
private void lstDinner_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Delete)
{
RemoveItemsFromDatabase();
}
}
private void lstDinner_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
_dinnerImage = new DinnerImageExtractor();
BitmapImage getImage = new BitmapImage();
if (lstDinner.SelectedItem != null)
{
getImage = _dinnerImage.GetDinnerImages(lstDinner.SelectedItem.ToString());
if (getImage != null)
{
DinnerImagePopup.Source = getImage;
}
else
{
DinnerImagePopup.Source = new BitmapImage(new Uri("/DinnerApplicationWPF;component/Menu/Images/noImage-icon-pink.png", UriKind.Relative));
}
PopupDinnerImage.IsOpen = true;
// PopupInstrcution.IsOpen = false;
}
}

I would suggest something like this
if ( lstDinner.SelectedItem == null)
{
output = _imageInserter.InsertImage(imageName, lstToys.SelectedItem.ToString());
PopupToysImage.IsOpen = true;
lstDinner.Databind();
}
Note: This may not work as I dont have your actual code. I have added DataBind() in the if statement, if the selected item was null. It should refresh the list.

Best thing is to use two different Listbox item templates for selected and unselected items. So without displaying popup, you can add button into the selected item template.

Are you disabling the ListBox while you select the image?
If so I believe by simply disabling the ListBox the SelectedItem will be set to null.
EDIT:
I imagine you want your event handlers (like the mouse double click) to happen when an item in your list is double clicked, not when the ListBox is double clicked. You need to change your XAML to this:
<ListBox Style="{DynamicResource ListBoxStyle1}" DisplayMemberPath="Dinner" BorderBrush="#FFF0F0F0" x:Name="lstDinner" FontSize="20" HorizontalAlignment="Left" Margin="0,110,0,72.667" Width="436" SelectionMode="Extended" PreviewMouseLeftButtonDown="MouseDownHandler" ScrollViewer.CanContentScroll="True" UseLayoutRounding="False" KeyDown="lstDinner_KeyDown">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<EventSetter Event="MouseDoubleClick" Handler="lstDinner_MouseDoubleClick" />
</Style>
</ListBox.Resources>
</ListBox>
My selected item does not come up null when I run this code.

Related

WPF DataGrid single click CheckBox does not enter Edit mode

I have a DataGrid with CheckBox-type column which should be editable with single click. This is easily achieved by using template column with CheckBox inside:
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<CheckBox BackGround="Red" IsChecked="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
the problem, however, is that single clicking will change the value without ever entering Edit mode. How can I ensure the edit mode is entered before changing the CheckBox value (all will single click)?
My best attempt on the problem was setting PreviewMouseLeftButtonDown on DataGridCell through style and forcing BeginEdit(). While this does begin edit, it is back to needing double click to interact.
private void DataGridCell_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (sender is DataGridCell cell && !cell.IsEditing && e.OriginalSource is DependencyObject source)
{
var checkBoxParent = VisualExtensions.GetVisualParent<CheckBox>(source);
if (checkBoxParent != null) // ensure CheckBox was clicked
{
cell.Focus();
ItemListDG.BeginEdit();
}
}
}
I have also tried handling Selected or GotFocus without any luck (breaks other types of interaction), CheckBox.Checked events cannot be used neither because they trigger on re/load.
In case of Selected event, the problem is that it enables single click edit on all columns even though it is handled on just one column (again, set through style):
private void DataGridCell_Selected(object sender, RoutedEventArgs e)
{
// second part of condition is always true, no matter what cell is clicked
if (sender is DataGridCell cell && cell.Column == ItemListDG.Columns[0])
{
// try to detect if the CheckBox column was clicked, if not return
if (sender != e.OriginalSource) // always false
return;
ItemListDG.BeginEdit(e); // always executes no matter the source
}
}
The event which should be handled is PreviewMouseLeftButtonUp, same like the case with Down event, after BeginEdit() is called the original CheckBox does not exist anymore and cannot handle the un/checking. The solution to doubleclick problem is to find the new representation of the CheckBox and either re-raise the event threre or toggle it manually:
private void DataGridCell_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (sender is DataGridCell cell && !cell.IsEditing && e.OriginalSource is UIElement source)
{
var actualSource = source is CheckBox ?
(CheckBox)source : VisualExtensions.GetVisualParent<CheckBox>(source);
if (actualSource != null)
{
ItemListDG.BeginEdit();
var newSource = cell.GetVisualChild<CheckBox>();
if (newSource != null)
{
newSource.IsChecked = !newSource.IsChecked;
}
}
}
}

Get names of control in code behind when clicked, wpf

I have a usercontrol and I want to get the names of the control user clicks on this usercontrol.
The Usercontrol xaml is given below:
<Grid Name="Grid1">
<ListView Name ="ListView1">
<listbox.resources>
<Datatemplate>
<Togglebutton Name="Button1">
</Togglebutton>
</Datatemplate>
</listbox.resources>
</Listview>
<Border Name="Border1">
<ContentControl>
</ContentControl>
</Border>
</Grid>
Now in my code behind, I wrote:
this.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(mouseclicked);
private void mouseclicked(object sender, MouseButtonEventArgs e)
{
var source = e?.OriginalSource as FrameworkElement;
if (source != null)
{
var parent = source.Parent as FrameworkElement;
string name = string.Empty;
if (parent != null)
{
name = parent.Name;
}
else
{
name = source.Name;
}
//If clicked outside the usercontrol, then usercontrol should close down
if (!(name.Contains("Border1") || name.Contains("Grid1"))) //very strange values sometimes null or empty
{
//then do something
}
}
}
How can I get the names of the controls used in my Usercontrol in codebehind?
You have gave the grid, border and listview a name in XAML. This you should access in codebehind and check in the mouseclicked event, which element is mouse over.
private void mouseclicked(object sender, MouseButtonEventArgs e)
{
if (ListView1.IsMouseOver && Grid1.IsMouseOver)
{
MessageBox.Show("List view clicked");
}
if (Border1.IsMouseOver && Grid1.IsMouseOver)
{
MessageBox.Show("Border1 clicked");
}
if (Grid1.IsMouseOver)
{
MessageBox.Show("Grid1 clicked");
}
}
This would may be not the best, but could work to get the names of controls the user has clicked.

Making controls transparent to mouse interaction in WPF

I am trying to make a WPF listbox replicate the behaviour of an old Winforms CheckedListBox, or the checked list box used in e.g. AnkhSVN. I have seen examples that show how to use a DataTemplate to create a check box for every time (e.g. Wpf CheckedListbox - how to get selected item), but this feels very clunky compared to the winforms control:
The logic of "If the user changes a check state, ensure that check state changes for all selected items" is not present by default.
The hit area to change an item from checked to unchecked is the box /and/ the title, rather than just the box as in Winforms
I can handle the first issue by adding a listener to the PropertyChanged event on each item in the bound collection, and if IsChecked changes, then set IsChecked to the same value for all currently selected items.
However, I cannot find a good solution to the second issue. By splitting the DataTemplate into a Checkbox with no title, and a TextBlock with the title, I can reduce the hit area to change the check state to only the desired square. However, all mouse interaction which hits the TextBlock does nothing - I would like it to behave the same as in a normal listbox, or in the dead space outside of the Textblock: If the user is holding shift, then select everything up to and including this item, if not, then clear the selection and select only this item. I could try to implement something where I handled Mouse* events on the TextBlock, but that seems brittle and inelegant - I'd be trying to recreate the exact behaviour of the ListBox, rather than passing events to the listbox.
Here's what I've got currently:
XAML:
<ListBox x:Name="_lstReceivers" SelectionMode="Extended" Margin="10,41,6,15"
ItemsSource="{Binding Receivers}">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsChecked}" IsHitTestVisible="True"/>
<TextBlock Text="{Binding Item}" Background="{x:Null}" IsHitTestVisible="False"/><!--Attempt to make it pass mouse events through. Doesn't work. Yuk.-->
</StackPanel>
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code behind to get the "Change all checks at the same time" logic (removed some error handling for clarity):
private void ListBoxItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var item = sender as CheckableItem<Receiver>;
if (item == null)
return;
if (e.PropertyName == nameof(CheckableItem<Receiver>.IsChecked))
{
bool newVal = item.IsChecked;
foreach (CheckableItem<Receiver> changeItem in _lstReceivers.SelectedItems)
{
changeItem.IsChecked = newVal;
}
}
}
By trying various combinations of Background = "{x:Null}" and IsHitTestVisible="False", I did manage to get the entire item to not respond to mouse click events - but I could not make it have only the Checkbox respond to mouse events, while everything else is passed to the ListBox for proper selection processing.
Any help would be greatly appreciated.
Answering my own question again.
Well, I couldn't find a clean way to do it, so I ended up setting the ListBoxItem to have IsHitTestVisible="False", and manually tracing mouse events using PreviewMouseDown.
Final code:
XAML:
<ListBox x:Name="_lstReceivers" SelectionMode="Extended" Margin="10,41,6,15"
ItemsSource="{Binding Receivers}" PreviewMouseDown="_lstReceivers_MouseDown">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxItem IsSelected="{Binding IsSelected}" IsHitTestVisible="False">
<StackPanel Orientation="Horizontal" Background="{x:Null}">
<CheckBox IsChecked="{Binding IsChecked}" IsHitTestVisible="True" Checked="CheckBox_Checked" Unchecked="CheckBox_Checked"/>
<TextBlock Text="{Binding Item}" Background="{x:Null}" IsHitTestVisible="False"/>
</StackPanel>
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code behind:
//Logic to handle allowing the user to click the checkbox, but have everywhere else respond to normal listbox logic.
private void _lstReceivers_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
Visual curControl = _lstReceivers as Visual;
ListBoxItem testItem = null;
//Allow normal selection logic to take place if the user is holding shift or ctrl
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl) || Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
return;
//Find the control which the user clicked on. We require the relevant ListBoxItem too, so we can't use VisualTreeHelper.HitTest (Or it wouldn't be much use)
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(curControl); i++)
{
var testControl = (Visual)VisualTreeHelper.GetChild(curControl, i);
var rect = VisualTreeHelper.GetDescendantBounds(testControl);
var pos = e.GetPosition((IInputElement)curControl) - VisualTreeHelper.GetOffset(testControl);
if (!rect.Contains(pos))
continue;
else
{
//There are multiple ListBoxItems in the tree we walk. Only take the first - and use it to remember the IsSelected property.
if (testItem == null && testControl is ListBoxItem)
testItem = testControl as ListBoxItem;
//If we hit a checkbox, handle it here
if (testControl is CheckBox)
{
//If the user has hit the checkbox of an unselected item, then only change the item they have hit.
if (!testItem.IsSelected)
dontChangeChecks++;
((CheckBox)testControl).IsChecked = !((CheckBox)testControl).IsChecked;
//If the user has hit the checkbox of a selected item, ensure that the entire selection is maintained (prevent normal selection logic).
if (testItem.IsSelected)
e.Handled = true;
else
dontChangeChecks--;
return;
}
//Like recursion, but cheaper:
curControl = testControl;
i = -1;
}
}
}
//Guard variable
int dontChangeChecks = 0;
//Logic to have all selected listbox items change at the same time
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
if (dontChangeChecks > 0)
return;
var newVal = ((CheckBox)sender).IsChecked;
dontChangeChecks++;
try
{
//This could be improved by making it more generic.
foreach (CheckableItem<Receiver> item in _lstReceivers.SelectedItems)
{
item.IsChecked = newVal.Value;
}
}
finally
{
dontChangeChecks--;
}
}
This solution works, but I don't like the coupling it introduces between my code and the exact behaviour of the ListBox implementation:
Checking the Keyboard state
It won't handle dragging if the user starts dragging inside a checkbox
It should happen on mouseup, not mousedown. But it's close enough for my needs.
PS: The bound class, even though it's irrelevant and obvious what it would have:
public class CheckableItem<T> : INotifyPropertyChanged
{
public T Item { get; set; }
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set
{
if (_isSelected == value)
return;
_isSelected = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSelected)));
}
}
private bool _checked;
public bool IsChecked
{
get => _checked;
set
{
if (_checked == value)
return;
_checked = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsChecked)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}

WPF - Remove a "User Control" Child from a StackPanel

I'm trying to make a WPF UI where the user can edit a query to search the database. The query is created according to what the consumer chooses from the comboboxes Like This and he can create as much filters as he wants as long as he clicks the Add new Condition button.
I created the comboboxes template as a User Control like this :
User control XAML:
<StackPanel Orientation="Horizontal" >
<Button
Name="DeleteFilter"
HorizontalAlignment="Left"
Margin="5"
Content="-"
Click="DeleteFilter_OnClick">
</Button>
<ComboBox
Text="Property"
x:Name="Property"
Width="100"
DataContext="{StaticResource SomeViewModel}"
ItemsSource="{Binding Properties}"
DisplayMemberPath="Name"
SelectionChanged="Property_OnSelectionChanged"/>
<ComboBox
Text="PropertyOperator"
x:Name="Operator"
ItemsSource="{Binding Operators}"
DisplayMemberPath="Name"
SelectionChanged="Operator_OnSelectionChanged">
</ComboBox>
<TextBox
x:Name="Value"
Text="Value"
TextAlignment="Center"
Width="100"
Margin="5"/>
</StackPanel>
Whenever the user clicks the Add new Condition button, I call this event:
private void AddFilterButton_OnClick(object sender, RoutedEventArgs e)
{
var conditionUserControl = new ConditionUserControl();
StackPanel.Children.Add(conditionUserControl);
}
Everything works correctly.
My Question:
How can I delete the User Control child from clicking the DeleteFilter button that exists in the User Control template.
I tried this:
StackPanel.Children.Remove(..);
to remove the child from my MainWindow but how to know which child the user clicked.
Try this:
private void DeleteFilter_OnClick(object sender, RoutedEventArgs e)
{
Button btn = sender as Button;
var conditionUserControl = FindParent<ConditionUserControl>(btn);
if (conditionUserControl != null)
{
var sp = FindParent<StackPanel>(conditionUserControl);
if (sp != null)
sp.Children.Remove(conditionUserControl);
}
}
private static T FindParent<T>(DependencyObject dependencyObject) where T : DependencyObject
{
var parent = VisualTreeHelper.GetParent(dependencyObject);
if (parent == null) return null;
var parentT = parent as T;
return parentT ?? FindParent<T>(parent);
}
Another answer to #mm8 answer is :
Update the AddFilterButton_OnClick:
I did this and the functionality works:
private void AddAndFilterButton_OnClick(object sender, RoutedEventArgs e)
{
var conditionUserControl = new ConditionUserControl();
StackPanel.Children.Add(conditionUserControl);
conditionUserControl.DeleteFilter.Click += (o, args) => StackPanel.Children.Remove(conditionUserControl);
}

WPF - How do determine the index of the current item in a listbox from button handler

i have a listbox with a data template that contains a button.
When the button is clicked I want to get in the button
click handler the index of the listbox item that was current??
How do I do this please?
Malcolm
More appropriate answer,
private void Button_Click(object sender, RoutedEventArgs e)
{
DependencyObject dep = (DependencyObject)e.OriginalSource;
while ((dep != null) && !(dep is ListViewItem))
{
dep = VisualTreeHelper.GetParent(dep);
}
if (dep == null)
return;
int index = lstBox.ItemContainerGenerator.IndexFromContainer(dep);
}
Hope the bellow code will help you.
private void Button_Click(object sender, RoutedEventArgs e)
{
var b = (Button)sender;
var grid = (Grid)b.TemplatedParent
var lstItem = (ListBoxItem)grid.TemplatedParent;
int index = lstBox.ItemContainerGenerator.IndexFromContainer(lstItem);
// rest of your code here...
}
And the XAML for the above assumed to be a DataTemplate on a ListBox named lstBox:
<DataTemplate x:Key="template">
<Grid>
<Button Click="Button_Click" Content="Press"/>
</Grid>
</DataTemplate>
Have you checked the "SelectedIndex" property of the listbox? It might be set by clicking on the button.
Probably way to late but using "IndexOf" on the listbox "Items" will give you the index #
Regards
myListbox.Items.CurrentItem seems be what you are looking for.
Hi you can use ContentPresenter.Content to get current item , instead of current index:
<DataTemplate DataType="{x:Type MyModel}">
<StackPanel Orientation="Horizontal" Margin="0 5">
<TextBlock Text="{Binding Title}" />
<Button Content="Active" Click="Button_Click" />
</StackPanel>
</DataTemplate>
and in code:
private void Button_Click(object sender, RoutedEventArgs e)
{
var button = e.Source as Button;
var contentPresenter = button.TemplatedParent as ContentPresenter;
var myModel = (MyModel)contentPresenter.Content;
}

Categories