I'm creating WPF application and in my settings panel I have couple of labels, textboxes, comboboxes and two buttons (Save) and (Cancel).
Xaml
<ComboBox x:Name="myCombobox" Grid.Column="1" Margin="18,372,4,0" VerticalAlignment="Top" Height="26" SelectionChanged="MyCombobox_SelectionChanged" />
I already have added items to my combobox :
myCombobox.Items.Add("Test1");
myCombobox.Items.Add("Test2");
myCombobox.Items.Add("Test3");
foreach (var item in myCombobox.Items)
if (item.Equals(Properties.Settings.Default.MyCombobox))
myCombobox.SelectedItem = item;
and added SelectionChanged event. This is how it looks:
private void MyCombobox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (myCombobox.SelectedItem.ToString().Equals("Test1"))
{
testGrid.Visibility = Visibility.Visible;
}
else if (myCombobox.SelectedItem.ToString().Equals("Test2") || myCombobox.SelectedItem.ToString().Equals("Test3"))
{
testGrid.Visibility = Visibility.Hidden;
}
}
When I click the Cancel button and restart my settings panel the items of my combobox are duplicated. (Same values twice).
I have tried to prevent this by adding to Cancel buttons click event
myCombobox.Items.Clear();
but at this point another problem exists (myCombobox.SelectedItem is null) and I get this error:
An exception of type 'System.NullReferenceException' occurred in
IdentificationStation.exe but was not handled in user code
How could I prevent comboboxes items to be duplicated? Or should I do the MyCombobox_SelectionChanged otherwice , any helps?
Can't you just avoid
'System.NullReferenceException'
by testing if myCombobox.SelectedItem is null on MyCombobox_SelectionChanged?
private void MyCombobox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (myCombobox.SelectedItem != null)
if (myCombobox.SelectedItem.ToString().Equals("Test1"))
{
testGrid.Visibility = Visibility.Visible;
}
else if (myCombobox.SelectedItem.ToString().Equals("Test2") || myCombobox.SelectedItem.ToString().Equals("Test3"))
{
testGrid.Visibility = Visibility.Hidden;
}
}
}
I don't think it's bad manner to do it like that.
Use this check to stop addition of duplicate items:
if(!myComboBox.Items.Contains("item"))
{
myComboBox.Items.Add("item");
}
Related
I have many forms with a lot of textboxes. And I want to add to that forms a button with IsDefault = true. Then fill any property and press enter. If I will not set UpdateSourceTrigger=PropertyChanged on textbox, it will not see my input.
The problem is that I do not want to add UpdateSourceTrigger=PropertyChanged to each textbox, combobox, checkbox, and etc.
Is there any way to trigger everything to write it's data to the source without adding UpdateSourceTrigger=PropertyChanged?
Call the BindingExpression.UpdateSource method in the click event handler for each of the Controls (TextBox, CheckBox, etc.) that you have in your Window/UserControl, like this:
private void ButtonBase_OnClick(object sender, RoutedEventArgs e) {
this.UpdateSourceTrigger(this);
MessageBox.Show($"Field1: {this.vm.Field1}\nField2: {this.vm.Field2}\nField3: {this.vm.Field3}");
}
public void UpdateSourceTrigger(UIElement element) {
if (element == null) return;
var children = LogicalTreeHelper.GetChildren(element);
foreach (var e in children) {
if (e is TextBox txtBox) {
var binding = txtBox.GetBindingExpression(TextBox.TextProperty);
binding?.UpdateSource();
}
if (e is CheckBox chkBox) {
var binding = chkBox.GetBindingExpression(CheckBox.IsCheckedProperty);
binding?.UpdateSource();
}
// add other types, like ComboBox or others...
// ...
this.UpdateSourceTrigger(e as UIElement);
}
}
I want to deny deletion of a row in case some there is a property with specific value, for example if product type is Steel I would like to deny user from deleting that row..
I'm setting source to my datagrid like this:
dataGridSourceList = new ObservableCollection<DatabaseItems>(TempController.Instance.SelectItemsByUserId(Globals.CurrentUser.Id));
dtgMainItems.ItemsSource = dataGridSourceList;
I saw there is a property CanUserDeleteRows
And I've added this to definition of my datagrid in xaml but I'm not sure how to apply this properly..
CanUserDeleteRows="{Binding ElementName=dtgMainItems, Path=SelectedItem.IsDeleteEnabled}"
Any kind of help would be awesome
Thanks
You could handle the CommandManager.PreviewCanExecute attached event:
private void OnPreviewCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (e.Command == DataGrid.DeleteCommand)
{
DatabaseItems selectedItem = dtgMainItems.SelectedItem as DatabaseItems;
if (selectedItem != null && !selectedItem.IsDeleteEnabled)
e.Handled = true;
}
}
XAML:
<DataGrid x:Name="dtgMainItems" CommandManager.PreviewCanExecute="Grid_PreviewCanExecute" />
I made a user control, which I called InputTextBox, that present text which the user can edit only after clicking the control:
<Grid>
<TextBox Name="box"
Text="{Binding RelativeSource={RelativeSource AncestorType=local:InputTextBlock}, Path=Text, Mode=TwoWay}"
Visibility="Hidden"
LostKeyboardFocus="box_LostKeyboardFocus"
KeyDown="box_KeyDown"/>
<Button Name="block"
Background="Transparent"
BorderThickness="0"
Click="block_Click">
<Button.Content>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=local:InputTextBlock}, Path=Text}" />
</Button.Content>
</Button>
</Grid>
When the user click the button the following callack is used:
private void block_Click(object sender, RoutedEventArgs e)
{
StartEdit();
}
public void StartEdit()
{
box.Visibility = Visibility.Visible;
block.Visibility = Visibility.Hidden;
box.CaretIndex = box.Text.Length;
Keyboard.Focus(box);
}
There are two more important events that are handled within the control. The first is when the control loses keyboard focus:
private void box_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
box.Visibility = Visibility.Hidden;
block.Visibility = Visibility.Visible;
}
And the other is when the user presses the TAB or ENTER keys:
private void box_KeyDown(object sender, KeyEventArgs e)
{
Key key = e.Key;
if (key == Key.Enter || key == Key.Tab)
{
RoutedEventArgs args = new RoutedEventArgs(KeyExitEvent, this, e.Key);
RaiseEvent(args);
}
}
This raises a simple routed event I registered for this control called KeyExit.
So, basically, it's like this control has two "modes": "edit mode" which the user can activate by simply clicking on the control, and "view mode", which the user can return to by giving any other control in the UI keyboard focus.
In my UI I have a stack panel with a bunch of these controls in it - each one is wrapped inside a class I created that is similar in concept to ListViewItem. The idea is that when the user is in edit mode inside one of the items in the stack panel and click the TAB or ENTER key, the next control in the panel will get into edit mode.
So, I have the following event callback:
private void item_KeyExit(object sender, RoutedEventArgs e)
{
FrameworkElement obj = e.OriginalSource as FrameworkElement;
if (obj != null)
{
var listItem = VisualTreeHelperUtils.FindFirstAncestorOfType<MyListItem>(obj);
if (listItem != null)
{
int itemIndex = stackPanelList.Children.IndexOf(listItem);
MyListItem nextItem = null;
if (itemIndex == ucSortableList.Children.Count - 1)
{
nextItem = stackPanelList.Children[itemIndex+1] as MyListItem;
}
if (nextItem != null)
{
var item = nextItem.DataContent; // property I made in MyListItem that gives access to the class it wraps
InputTextBlock block = item as InputTextBlock;
if (block != null)
{
block.StartEdit();
}
}
}
}
}
Everything is called properly, but immediately after all of it is done, the previous item, which I tabbed out of, gets the keyboard focus back and cause no item in the stack to be in edit mode. So, if, for example, the first item in the stack was in edit mode, once the user hit the tab key, the second item in the stack gets into edit mode, but then the first item gets keyboard focus back immediately. Can anybody understand why this is happening?
StackPanel Focusable is false by default. Suggest ensure it's true.
Also for checking indeces, one generally wants the index to be < count. So your index + 1 could == count if it's == count - 1. Don't you want:
if (itemIndex < ...Count - 1)
That way, item index + 1 will always be <= Count - 1 ?
if (itemIndex == ucSortableList.Children.Count - 1)
{
nextItem = stackPanelList.Children[itemIndex+1] as MyListItem;
}
Also you can use box.Focus() instead of Keyboard.Focus(box) but that may not be necessary.
I subscribed to a SelectionChangedEvent on a ComboBox in a DataGrid with the following code:
public static DataGridTemplateColumn CreateComboboxColumn(string colName, Binding textBinding, SelectionChangedEventHandler selChangedHandler = null)
{
var cboColumn = new DataGridTemplateColumn {Header = colName};
...
if (selChangedHandler != null)
cboFactory.AddHandler(Selector.SelectionChangedEvent, selChangedHandler);
...
return cboColumn;
}
The handler I actually register contains:
private void ComboBoxSelectionChangedHandler(object sender, SelectionChangedEventArgs e)
{
Console.WriteLine(#"selectHandler");
var cboBox = sender as ComboBox;
if (cboBox == null)
return;
if (cboBox.IsDropDownOpen) // a selection in combobox was made
{
CommitEdit();
}
else // trigger the combobox to show its list
cboBox.IsDropDownOpen = true;
}
... and is located in my custom DataGrid class.
If I select an item in the ComboBox, e.AddedItems and cboBox.SelectedItem contains the selected value, but nothing is changed on CommitEdit().
What I want is to force a commit to directly update the DataGrid's ItemsSource, when the user selects an item in the drop-down-list. Normally this is raised if the control looses focus...
The link in the solution found in this thread is not available any more and I don't know how to use this code.
I created a tricky, but working, solution for my problem. Here's the modified handler from above:
private void ComboBoxSelectionChangedHandler(object sender, SelectionChangedEventArgs e)
{
Console.WriteLine(#"selectHandler");
var cboBox = sender as ComboBox;
if (cboBox == null)
return;
if (cboBox.IsDropDownOpen) // a selection in combobox was made
{
cboBox.Text = cboBox.SelectedValue as string;
cboBox.MoveFocus(new TraversalRequest(FocusNavigationDirection.Right));
}
else // user wants to open the combobox
cboBox.IsDropDownOpen = true;
}
Because my ComboBoxColumn is a custom DataGridTemplateColumn I force it to show its list, when the user first selects the cell.
To change the bound items value I manually overwrite the displayed text with recently selected item and force the UI to select another item (in this case the control to the right) to make an implicit call to CellEditEnding event, which (in my case) commits the whole row:
private bool _isManualEditCommit = false;
private void _CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
// commit a manual edit
// this if-clause prevents double execution of the EditEnding event
if (!_isManualEditCommit)
{
Console.WriteLine(#"_CellEditEnding() manualeditcommit");
_isManualEditCommit = true;
CommitEdit(DataGridEditingUnit.Row, true);
_isManualEditCommit = false;
checkRow(e.Row);
}
}
Maybe I could help somebody with this "dirty" solution ;-)
When I type in the combobox I automatically opens enables the dropdown list
searchComboBox.IsDropDownOpen = true;
The problem here is - the text gets highlighted and the next keystrock overwrites the previous text.
How can I disable the text highlighting when ComboBox DropDown opens up?
I had this very same issue and like some of the users being new to WPF, struggled to get the solution given by Einar Guðsteinsson to work. So in support of his answer I'm pasting here the steps to get this to work. (Or more accurately how I got this to work).
First create a custom combobox class which inherits from the Combobox class. See code below for full implementation. You can change the code in OnDropSelectionChanged to suit your individual requirements.
namespace MyCustomComboBoxApp
{
using System.Windows.Controls;
public class MyCustomComboBox : ComboBox
{
private int caretPosition;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var element = GetTemplateChild("PART_EditableTextBox");
if (element != null)
{
var textBox = (TextBox)element;
textBox.SelectionChanged += OnDropSelectionChanged;
}
}
private void OnDropSelectionChanged(object sender, System.Windows.RoutedEventArgs e)
{
TextBox txt = (TextBox)sender;
if (base.IsDropDownOpen && txt.SelectionLength > 0)
{
caretPosition = txt.SelectionLength; // caretPosition must be set to TextBox's SelectionLength
txt.CaretIndex = caretPosition;
}
if (txt.SelectionLength == 0 && txt.CaretIndex != 0)
{
caretPosition = txt.CaretIndex;
}
}
}
Ensure that this custom combo class exists in the same project. THen you can use the code below to reference this combo in your UI.
<Window x:Class="MyCustomComboBoxApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cc="clr-namespace:MyCustomComboBoxApp"
Title="MainWindow" Height="350" Width="525" FocusManager.FocusedElement="{Binding ElementName=cb}">
<Grid>
<StackPanel Orientation="Vertical">
<cc:MyCustomComboBox x:Name="cb" IsEditable="True" Height="20" Margin="10" IsTextSearchEnabled="False" KeyUp="cb_KeyUp">
<ComboBoxItem>Toyota</ComboBoxItem>
<ComboBoxItem>Honda</ComboBoxItem>
<ComboBoxItem>Suzuki</ComboBoxItem>
<ComboBoxItem>Vauxhall</ComboBoxItem>
</cc:MyCustomComboBox>
</StackPanel>
</Grid>
</Window>
Thats it! Any questions, please ask! I'll do my best to help.
THanks to Einar Guðsteinsson for his solution!
Better late than never and if some one else hit this proplem he might use this.
There is away todo this if you override combobox.
First get handle on the textbox that is used in the template and register to selectionchanged event.
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var element = GetTemplateChild("PART_EditableTextBox");
if (element != null)
{
var textBox = (TextBox)element;
textBox.SelectionChanged += OnDropSelectionChanged;
}
}
private void OnDropSelectionChanged(object sender, RoutedEventArgs e)
{
// Your code
}
Then in the event handler you can set the selection again like you want it to be. In my case I was calling IsDropDownOpen in code. Saved selection there then put it back in the event handler. Ugly but did the trick.
Further to clsturgeon's answer, I have solved the issue by setting the selection when DropDownOpened event occurred:
private void ItemCBox_DropDownOpened(object sender, EventArgs e)
{
TextBox textBox = (TextBox)((ComboBox)sender).Template.FindName("PART_EditableTextBox", (ComboBox)sender);
textBox.SelectionStart = ((ComboBox)sender).Text.Length;
textBox.SelectionLength = 0;
}
I think in the Solution provided by Andrew N there is something missing as when I tried it out the Selection Changed event of the TextBox was placing the caret at the wrong place. So I made this change to solve that.
namespace MyCustomComboBoxApp { using System.Windows.Controls;
public class MyCustomComboBox : ComboBox
{
private int caretPosition;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var element = GetTemplateChild("PART_EditableTextBox");
if (element != null)
{
var textBox = (TextBox)element;
textBox.SelectionChanged += OnDropSelectionChanged;
}
}
private void OnDropSelectionChanged(object sender, System.Windows.RoutedEventArgs e)
{
TextBox txt = (TextBox)sender;
if (base.IsDropDownOpen && txt.SelectionLength > 0)
{
caretPosition = txt.SelectionLength; // caretPosition must be set to TextBox's SelectionLength
txt.CaretIndex = caretPosition;
}
if (txt.SelectionLength == 0 && txt.CaretIndex != 0)
{
caretPosition = txt.CaretIndex;
}
}
}
When a comboxbox gains focus you can disable the text highlighting (i.e. by selecting no text upon the GotFocus event). However, when you pulldown the combobox the system is going to locate the item in the list and make that the selected item. This in turn automatically highlights the text. If I understand the behaviour you are looking for, I do not believe it is fully possible.
I was able to fix it using a modified answer from Jun Xie. Assuming you are using the keyUp event for your combobox search, I found an edge case in my custom use case that would still overwrite the text:
Type in the combobox for the first time. Text is fine.
Use up and down arrow keys to select an item in the list, but not "committing" the change (pressing enter, for example, and closing the dropDown selections. Note the text is highlighted at this point like clsturgeon points out.
Try to type in the textbox again. In this case the text will be over-ridden because the dropdown was still open hence the event never fired to clear the highlight.
The solution is to check if an item is selected. Here's the working code:
XAML:
<ComboBox x:Name="SearchBox" IsEditable="True" KeyUp="SearchBox_KeyUp"
PreviewMouseDown="SearchBox_PreviewMouseDown" IsTextSearchEnabled="False"
DropDownOpened="SearchBox_DropDownOpened">
</ComboBox>
Code:
private void SearchBox_KeyUp(object sender, KeyEventArgs e)
{
SearchBox.IsDropDownOpen = true;
if (e.Key == Key.Down || e.Key == Key.Up)
{
e.Handled = true;
//if trying to navigate but there's noting selected, then select one
if(SearchBox.Items.Count > 0 && SearchBox.SelectedIndex == -1)
{
SearchBox.SelectedIndex = 0;
}
}
else if (e.Key == Key.Enter)
{
//commit to selection
}
else if (string.IsNullOrWhiteSpace(SearchBox.Text))
{
SearchBox.Items.Clear();
SearchBox.IsDropDownOpen = false;
SearchBox.SelectedIndex = -1;
}
else if (SearchBox.Text.Length > 1)
{
//if something is currently selected, then changing the selected index later will loose
//focus on textbox part of combobox and cause the text to
//highlight in the middle of typing. this will "eat" the first letter or two of the user's search
if(SearchBox.SelectedIndex != -1)
{
TextBox textBox = (TextBox)((ComboBox)sender).Template.FindName("PART_EditableTextBox", (ComboBox)sender);
//backup what the user was typing
string temp = SearchBox.Text;
//set the selected index to nothing. sets focus to dropdown
SearchBox.SelectedIndex = -1;
//restore the text. sets focus and highlights the combobox text
SearchBox.Text = temp;
//set the selection to the end (remove selection)
textBox.SelectionStart = ((ComboBox)sender).Text.Length;
textBox.SelectionLength = 0;
}
//your search logic
}
}
private void SearchBox_DropDownOpened(object sender, EventArgs e)
{
TextBox textBox = (TextBox)((ComboBox)sender).Template.FindName("PART_EditableTextBox", (ComboBox)sender);
textBox.SelectionStart = ((ComboBox)sender).Text.Length;
textBox.SelectionLength = 0;
}
An alternative. Prevent the framework from messing with selection.
public class ReflectionPreventSelectAllOnDropDown
{
private static readonly PropertyInfo edtbPropertyInfo;
static ReflectionPreventSelectAllOnDropDown()
{
edtbPropertyInfo = typeof(ComboBox).GetProperty("EditableTextBoxSite", BindingFlags.NonPublic | BindingFlags.Instance);
}
public void DropDown(ComboBox comboBox)
{
if (!comboBox.IsDropDownOpen)
{
var edtb = edtbPropertyInfo.GetValue(comboBox);
edtbPropertyInfo.SetValue(comboBox, null);
comboBox.IsDropDownOpen = true;
edtbPropertyInfo.SetValue(comboBox, edtb);
}
}
}