WPF: Raise programmatically a SelectionChangedEvent with an object - c#

MouseDoubleClick opens a new window in which I update the DataTable by changing the content of TextBoxes. After updating a DataTable, I need to raise a SelectionChangedEvent in order to update strings to the correct values (SelectionChangedEvent triggers when you select a row in the DataGrid). That would be simple enough, if I wasn't programmatically selecting the same row after refreshing the DataGrid, which means that the selection technically never changes and the values won't update unless I select another row.
I solved the issue by changing the index to -1 then changing it back to the previous value, but I would rather just call the handler directly DG_Part_SelectionChanged();. Refactoring the logic into a new function doesn't work.
public void DG_Part_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (CurrentPartID != 0)
{
int lastId = CurrentPartID;
EditWindow ew = new EditWindow(CurrentPartID)
{
Owner = this
};
ew.ShowDialog();
if (Global.invokeDataGridParts == "yes")
{
// Refreshes the datagrid with an updated datatable
InvokeDataGridPart();
// Finds and selects the new index position of the modified row
SqlPartsSetToRow(lastId);
// Scrolls into view
dg_part.ScrollToCenterOfView(dg_part.Items[dg_part.SelectedIndex]);
// Highlights the row
Dispatcher.Invoke(DispatcherPriority.SystemIdle, new Action(() =>
{
DataGridRow row = (DataGridRow)dg_part.ItemContainerGenerator.ContainerFromIndex(dg_part.SelectedIndex);
row.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
));
// Restores index so that you may re-select the previous selection correctly
int saveIndex = dg_part.SelectedIndex;
dg_part.SelectedIndex = -1;
dg_part.SelectedIndex = saveIndex;
}
}
}
public void DG_Part_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
DataGrid gd = (DataGrid)sender;
if (gd.SelectedItem is DataRowView row_selected)
{
Global.del = row_selected["DEL"].ToString();
Global.delez = row_selected["DELEZ"].ToString();
Global.cr_tu = row_selected["CRTU"].ToString();
Global.st_clanov = row_selected["ST"].ToString();
Global.lastnik = row_selected["LASTNIK"].ToString();
Global.naslov = row_selected["NASLOV"].ToString();
Global.ps = row_selected["PS"].ToString();
Global.obmocje2 = row_selected["OBMOCJE"].ToString();
Global.drzava = row_selected["DRZAVA"].ToString();
Global.emso = row_selected["EMSO"].ToString();
Global.maticna_st = row_selected["MATICNA"].ToString();
Global.reference = row_selected["REFERENCE"].ToString();
Global.opis = row_selected["OPIS"].ToString();
Global.opomba = row_selected["OPOMBA"].ToString();
}
}

Had to change DataGrid gd = (DataGrid)sender; into DataGrid gd = (DataGrid)dg_part; and refactor the logic:
public void DG_Part_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
DG_Part_Selection();
}
public void DG_Part_Selection()
{
DataGrid gd = (DataGrid)dg_part;
if (gd.SelectedItem is DataRowView row_selected)
{
Global.del = row_selected["DEL"].ToString();
Global.delez = row_selected["DELEZ"].ToString();
Global.cr_tu = row_selected["CRTU"].ToString();
Global.st_clanov = row_selected["ST"].ToString();
Global.lastnik = row_selected["LASTNIK"].ToString();
Global.naslov = row_selected["NASLOV"].ToString();
Global.ps = row_selected["PS"].ToString();
Global.obmocje2 = row_selected["OBMOCJE"].ToString();
Global.drzava = row_selected["DRZAVA"].ToString();
Global.emso = row_selected["EMSO"].ToString();
Global.maticna_st = row_selected["MATICNA"].ToString();
Global.reference = row_selected["REFERENCE"].ToString();
Global.opis = row_selected["OPIS"].ToString();
Global.opomba = row_selected["OPOMBA"].ToString();
}
}
Then simply call the handler:
public void DG_Part_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (CurrentPartID != 0)
{
int lastId = CurrentPartID;
EditWindow ew = new EditWindow(CurrentPartID)
{
Owner = this
};
ew.ShowDialog();
if (Global.invokeDataGridParts == "yes")
{
// Refreshes the datagrid with an updated datatable
InvokeDataGridPart();
// Finds and selects the new index position of the modified row
SqlPartsSetToRow(lastId);
// Scrolls into view
dg_part.ScrollToCenterOfView(dg_part.Items[dg_part.SelectedIndex]);
// Highlights the row
Dispatcher.Invoke(DispatcherPriority.SystemIdle, new Action(() =>
{
DataGridRow row = (DataGridRow)dg_part.ItemContainerGenerator.ContainerFromIndex(dg_part.SelectedIndex);
row.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
));
// Restores index so that you may re-select the previous selection correctly
DG_Part_Selection();
}
}
}

Related

Why does autocomplete in my Winforms ComboBox change the cursor location when typing?

I have a WinForms ComboBox within an Outlook Add-in which populates the drop-down with user names, when the user types names in the edit box. When the user reaches the third character. The data source of the ComboBox is assigned at this point, and the curser jumps to the beginning of the edit box instead of staying at the end.
The behavior I want is that the cursor stays at the end of the string I am typing, even when the drop-down is populated with more data.
I've tried hacking this with send keys, but it doesn't always work. The code which reads the keys below is in the key pressed event.
private void comboBox1_KeyPress(object sender, KeyEventArgs e)
{
var acceptableKeys = ConfigurationManager.AppSettings["AcceptableKeys"];
if (cmbAssignedTo.Text.Length > 2 && acceptableKeys.Contains(e.KeyCode.ToString().ToUpper()) && e.Modifiers == Keys.None)
{
var request = RestHandler.CreateRequest(ConfigurationManager.AppSettings["ContactsSearchResource"] + cmbAssignedTo.Text.Trim(), Method.GET);
var response = RestHandler.ExecuteRequest(request, ConfigurationManager.AppSettings["myServiceURL"]);
this.cmbAssignedTo.DataSourceChanged -= new System.EventHandler(this.cmbAssignedTo_DataSourceChanged);
//Assign a new data source
DataHandler.UpdateComboboxDataSource(cmbAssignedTo, response.Content);
this.cmbAssignedTo.DataSourceChanged += new System.EventHandler(this.cmbAssignedTo_DataSourceChanged);
}
e.Handled = true;
}
Edit
internal static void UpdateComboboxDataSource(ComboBox cmbAssignedTo, string data)
{
var list = BuildAssignmentList(data);
if ((list.Count() == 0 && cmbAssignedTo.Items.Count == 0) || list.Count() > 0)
{
var savedText = cmbAssignedTo.Text;
cmbAssignedTo.DataSource = list;
cmbAssignedTo.SelectedValue = "";
cmbAssignedTo.Text = savedText;
SendKeys.Send("{end}");
}
if (cmbAssignedTo.Items.Count > 0)
{
cmbAssignedTo.DroppedDown = true;
Cursor.Current = Cursors.Default;
}
}
I don't see how I can update the dropdown without changing the DataSource and that change appears to cause the cursor to jump. Should I try different event than KeyPressed? Is there some other solution I'm missing?
As another hack you can play with ComboBox's SelectionStart property:
int i = comboBox1.SelectionStart;
comboBox1.DataSource = new System.Collections.Generic.List<string>(){"aaaaaa", "bbbbbb", "ccccccc"};
comboBox1.SelectionStart = i;
This code changes the DataSource and retain the cursor position. If you want cursor to be always at end - set SelectionStart to comboBox1.Text.Length.
UPD: To fight against the "first item selection" you may use another hack:
private bool cbLock = false;
private void comboBox1_TextChanged(object sender, EventArgs e)
{
// lock is required, as this event also will occur when changing the selected index
if (cbLock)
return;
cbLock = true;
int i = comboBox1.SelectionStart;
// store the typed string before changing DS
string text = comboBox1.Text.Substring(0, i);
List<string> ds = new System.Collections.Generic.List<string>() { "aaaaaa", "aaabbb", "aaacccc" };
comboBox1.DataSource = ds;
// select first match manually
for (int index = 0; index < ds.Count; index++)
{
string s = ds[index];
if (s.StartsWith(text))
{
comboBox1.SelectedIndex = index;
break;
}
}
// restore cursor position and free the lock
comboBox1.SelectionStart = i;
cbLock = false;
}
When typing "aaab" it selects the "aaabbb" string.

WPF How to create custom DataGrid with custom DataGridCells?

I'm looking to create a custom DataGrid such that users can attach notes to each cell using a popup input box. Currently I've created a CustomDataGrid class, inheriting from DataGrid, with a ContextMenu that has an option to add a note. When the user chooses to add a note, I find the selected cell, an input box is opened and returns the response, and I store it in a list of lists of strings, where each list of string represents a row. This doesn't work all the time, however, because sometimes no cell is selected, and I get an error message saying: 'Object reference not set to an instance of an object.'. I'm thinking of creating a CustomDataGridCell class, inheriting from DataGridCell, which has its own ContextMenu and note string. The question is, how would I make all cells in my CustomDataGrid a CustomDataGridCell? Is there a better way to do this?
Here is my current CustomDataGrid class:
public class CustomDataGrid : DataGrid
{
MenuItem miAddNote;
List<List<string>> notes;
public CustomDataGrid()
{
notes = new List<List<string>>();
miAddNote = new MenuItem();
miAddNote.Click += MiAddNote_Click;
miAddNote.Header = "Add a note";
this.ContextMenu = new ContextMenu();
this.ContextMenu.Items.Add(miAddNote);
}
private void MiAddNote_Click(object sender, RoutedEventArgs e)
{
try
{
int rowIndex = this.SelectedIndex;
int colIndex = this.SelectedCells[0].Column.DisplayIndex;
InputBox ib = new InputBox(notes[rowIndex][colIndex]);
if (ib.ShowDialog() == true)
{
notes[rowIndex][colIndex] = ib.Response;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
protected override void OnLoadingRow(DataGridRowEventArgs e)
{
base.OnLoadingRow(e);
int numColumns = this.Columns.Count;
List<string> newRow = new List<string>();
for (int i = 0; i < numColumns; ++i)
{
newRow.Add("");
}
notes.Add(newRow);
}
}
The question is, how would I make all cells in my CustomDataGrid a CustomDataGridCell?
There is no easy way to to this I am afraid. And it is not really necessary to create a custom cell type just to get rid of the exception.
Is there a better way to do this?
You should simply check whether there are any selected cells before trying to access any:
private void MiAddNote_Click(object sender, RoutedEventArgs e)
{
int rowIndex = this.SelectedIndex;
if (rowIndex != -1 && SelectedCells != null && SelectedCells.Count > 0)
{
int colIndex = this.SelectedCells[0].Column.DisplayIndex;
InputBox ib = new InputBox(notes[rowIndex][colIndex]);
if (ib.ShowDialog() == true)
{
notes[rowIndex][colIndex] = ib.Response;
}
}
}

How to Set Index of ListPicker without Executing ValueChanged Event

This problem has been driving me absolutely crazy. I have a ListPicker that is populated dynamically with several items. I have placed declared my SelectionChanged event handler in the Loaded event of my page. When the user clicks a certain item on the page, the ListPicker visibility will toggle from Collpased to Visible, and I set the values of the ListPicker. The issue is, the index of the ListPicker will be based upon the users settings, so out of three items the current index may be 1, not 0 as is the default. I need to show 1 as the current item in the ListPicker without the SelectionChanged event firing (which performs operations based on the current index). Then and only then when the user changes the selected item himself or herself do I need the SelectionChanged event to fire.
The main reason for this is not only does the user need to see his or her current setting in the ListPicker when it is displayed, but on the SelectionChanged event operations occur which overwrite what currently exists which is extremely confusing and not supposed to occur unless the user specifies.
What I currently have is as follows
XAML
<toolkit:ListPicker x:Name="lp" Visibility="Collapsed" Margin="12" Width="300"/>
XAML.CS
private void Page_Loaded(object sender, RoutedEventArgs e)
{
lp.SelectionChanged += lp_SelectionChanged;
}
void EditableEllipse_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
if (sender != null)
{
DependencyObject tappedElement = e.OriginalSource as UIElement;
// find parent UI element of type PhotoThumbnail
//PhotoThumbnail i = this.FindParentOfType<PhotoThumbnail>(tappedElement);
i = this.FindParentOfType<PhotoThumbnail>(tappedElement);
if (i != null)
{
BuildControl(i);
}
}
}
private void BuildControl(PhotoThumbnail pp)
{
switch(pp.NName)
{
case "flip":
List<ListPickerItem> l = new List<ListPickerItem>();
l.Add(new ListPickerItem { Name = "Flip_Vertical", Content = AppResources.App_Flip_Vertical });
l.Add(new ListPickerItem { Name = "Flip_Horizontal", Content = AppResources.App_Flip_Horizontal });
l.Add(new ListPickerItem { Name = "Flip_Both", Content = AppResources.App_Flip_Both });
lp.ItemsSource = l; //Code execution jumps from here to ValueChanged event immediately
lp.Visibility = Visibility.Visible;
lp.SelectedIndex = Settings.Flip.Value - 1;
break;
..
}
}
private async void lp_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (lp.SelectedIndex != -1) //always defaults to = 0
{
var item = (sender as ListPicker).SelectedItem;
string name = ((ListPickerItem)item).Name;
if (name != null)
{
switch (name)
{
case "Flip_Vertical":
Settings.Flip.Value = 1;
..perform process based on current Setting.Flip.Value.. break;
case "Flip_Horizontal":
Settings.Flip.Value = 2;
..perform process based on current Setting.Flip.Value..
break;
case "Flip_Both":
Settings.Flip.Value = 3;
..perform process based on current Setting.Flip.Value..
break;
...
}
}
}
Try using an order of operation technique (by unhooking and rehooking the event)
private void BuildControl(PhotoThumbnail pp)
{
switch(pp.NName)
{
case "flip":
// unhook event
lp.SelectionChanged -= lp_SelectionChanged;
List<ListPickerItem> l = new List<ListPickerItem>();
l.Add(new ListPickerItem { Name = "Flip_Vertical", Content = AppResources.App_Flip_Vertical });
l.Add(new ListPickerItem { Name = "Flip_Horizontal", Content = AppResources.App_Flip_Horizontal });
l.Add(new ListPickerItem { Name = "Flip_Both", Content = AppResources.App_Flip_Both });
lp.ItemsSource = l; //Code execution jumps from here to ValueChanged event immediately
lp.Visibility = Visibility.Visible;
lp.SelectedIndex = Settings.Flip.Value - 1;
// after setting the index, rehook the event
lp.SelectionChanged += lp_SelectionChanged;
break;
..
}
}

How to select an item from a ListBox without using SelectionChange event

I am Silverlight developer and coding in C# to select an item from a list and display the selected item in the textBlock nearby.
My code to do so is:
ListBox lines = new ListBox();
TextBlock txtblkShowSelectedValue = new TextBlock();
ScrollViewer scrollViewer = new ScrollViewer();
scrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Visible;
lines.ItemsSource = param.Component.Attributes.Items;
Grid.SetColumn(lines, 1);
Grid.SetRow(lines, LoopCount);
childGrid.Children.Add(lines);
lines.SelectedIndex = 0;
lines.SelectedItem = param.Component.Attributes.Items;
The problem is how to select a value and how to display it in textblock "txtblkShowSelectedValue " ? because I cannot declare textblock and List variable globally because of current condition if I use selectionChange event
EDIT: The current scenario is :(lines (List) is in different function so it's not in scope of List_SelectionChanged() function)
private static Grid GenerateList(Parameter param, int LoopCount, Grid g)
{
Grid childGrid = new Grid();
ColumnDefinition colDef1 = new ColumnDefinition();
ColumnDefinition colDef2 = new ColumnDefinition();
ColumnDefinition colDef3 = new ColumnDefinition();
childGrid.ColumnDefinitions.Add(colDef1);
childGrid.ColumnDefinitions.Add(colDef2);
childGrid.ColumnDefinitions.Add(colDef3);
TextBlock txtblk1ShowStatus = new TextBlock();
TextBlock txtblkLabel = new TextBlock();
ListBox lines = new ListBox();
ScrollViewer scrollViewer = new ScrollViewer();
scrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Visible;
lines.ItemsSource = param.Component.Attributes.Items;
Grid.SetColumn(lines, 1);
Grid.SetRow(lines, LoopCount);
childGrid.Children.Add(lines);
lines.SelectedIndex = 0;
lines.SelectedItem = param.Component.Attributes.Items;
lines.SelectionChanged += new SelectionChangedEventHandler(List_SelectionChanged);
lines.SelectedIndex = lines.Items.Count - 1;
g.Children.Add(childGrid);
return (g);
}
static void List_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
MessageBox.Show("clist _SelectionChanged1");
TextBlock txtblk1ShowStatus = new TextBlock();
txtblk1ShowStatus.Text = lines[(sender as ListBox).SelectedIndex];
}
This could be streamlined, but should work as a quick 'n dirty example of one way to solve the problem...
void lb_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Grid g = null;
ListBox lb = sender as ListBox;
if (lb != null && lb.SelectedIndex >= 0)
{
// Find the top-level grid
var parent = VisualTreeHelper.GetParent(lb);
while (parent != null)
{
if (parent.GetType() == typeof(Grid))
{
if ((parent as Grid).Name.Equals("LayoutRoot"))
{
g = (Grid)parent;
break;
}
}
parent = VisualTreeHelper.GetParent(parent);
}
// Found the LayoutRoot, find the textblock
if (g != null)
{
for (int i = 0; i < g.Children.Count; i++)
{
var child = VisualTreeHelper.GetChild(g, i);
if (child is TextBlock)
{
(child as TextBlock).Text = (string)lb.SelectedItem;
break;
}
}
}
}
}
You could also name your textblock and search for that (as I did for "LayoutRoot").
Obviously, this code assumes the textblock is a child of the top-level Grid. Implementing a recursive search wouldn't be difficult.
lines.SelectionChanged+=new System.EventHandler(this.UpdateTextBlock); // add selectionchanged even for your listbox;
private void UpdateTextBlock(object sender, SelectionChangedEventArgs e)
{
txtblkShowSelectedValue.Text=this.lines[(sender as Listbox).SelectedIndex].ToString(); // just edit the content of your texblock
}
EDIT : thank you, and sorry to be late :-)
try this :
add parameter for the function, as this :
lines.SelectionChanged += new SelectionChangedEventHandler(List_SelectionChanged)
change parameter of this function and set your textblock as this :
static void List_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
MessageBox.Show("clist _SelectionChanged1");
txtblkShowSelectedValue.Text=this.lines[(sender as Listbox).SelectedIndex].ToString()
}
Afteralli solved the problem like this:
lines.SelectionChanged += (o, e) =>
{
MessageBox.Show("clist _SelectionChanged1");
txtblk1ShowStatus.Text = lines.SelectedItem.ToString();
};
lines.SelectedIndex = lines.Items.Count - 1;
in my function GenerateList(..)

Strange behaviour on DataGridView, focused cells do not scroll horizontally

Try this...
Create Form1, don't change size, add a dataGridView1 and set its anchor to left, top and right, then...
public class Form1 : Form
{
public Form1()
{
InitializeComponent();
dataGridView1.AutoGenerateColumns = true;
var source = new BindingList<Comic>(GetComics());
dataGridView1.DataSource = source;
}
private List<Comic> GetComics()
{
var comics = new List<Comic>();
comics.Add(new Comic() { id = "1", title = "IronMan", editr = "LayneBooks", pages = 65, owned = true });
comics.Add(new Comic() { id = "2", title = "The Hulk", editr = "LayneBooks", pages = 48, owned = false });
comics.Add(new Comic() { id = "3", title = "Superman", editr = "DCCore", pages = 72, owned = true });
return comics;
}
// Custom class, source for grid
private class Comic
{
public string id { get; set; }
public string title { get; set; }
public string editr { get; set; }
public int pages { get; set; }
public bool owned { get; set; }
}
As we know, we can go from cell to cell with arrow keys, or with Tab key (when StandardTab = false), and reach the grid corners with Ctrl + arrow keys.
Everything is OK with this code, the cells are focused and selected with keys, but when the columns width are manually changed, the not visible cells are focused, but not showed.
For example, using above code, if the width of first and second columns is reduced to a 20 - 30% of its original size, then, the right corner is not reached while pressing Ctrl + right key.
If the third column width is also reduced, and we try to reach the last one with right keys (or Tab), the cell is focused, but not showed completely (or absolutely).
How to solve this?, change columns width and get the same beahviour as when are autogenerated. Is there any property that I am missing?.
My goal is navigate through cells with keys, and show a context menu when Keys.Apps is pressed, but can't do it if the cell it is not visible.
Thanks!
There is no property that solve this behavior.
Please try this code.
public Form1()
{
InitializeComponent();
dataGridView1.AutoGenerateColumns = true;
var source = new BindingList<Comic>(GetComics());
dataGridView1.DataSource = source;
dataGridView1.CurrentCellChanged += dataGridView1_CurrentCellChanged;
}
private void dataGridView1_CurrentCellChanged(object sender, EventArgs e)
{
if (Control.MouseButtons.HasFlag(MouseButtons.Left)) return;
var dgv = (DataGridView)sender;
if (dgv.CurrentCell == null) return;
var dispWidth = dgv.ClientSize.Width - dgv.CurrentRow.HeaderCell.Size.Width;
int columnsWidthSum = 0;
for (int i = dgv.CurrentCell.ColumnIndex; i >= 0; i--)
{
columnsWidthSum += dgv.Columns[i].Width;
if (dispWidth >= columnsWidthSum || dispWidth >= dgv.Columns[i].Width)
{
dgv.FirstDisplayedScrollingColumnIndex = i;
break;
}
}
}
Thank you user3093781 very much for your answer.
I based my answer on yours.
This code worked for me.
private void dataGridView_CurrentCellChanged(object sender, EventArgs e) {
if ( Control.MouseButtons.HasFlag(MouseButtons.Left) )
return;
DataGridView dgv = (DataGridView)sender;
if ( dgv.CurrentCell == null || dgv.Columns.Count == 0 )
return;
dgv.FirstDisplayedScrollingColumnIndex =
dgv.FirstDisplayedScrollingColumnIndex > dgv.CurrentCell.ColumnIndex ?
dgv.CurrentCell.ColumnIndex :
dgv.FirstDisplayedScrollingColumnIndex
;
}

Categories