I have 3 columns in a gridview - code,Qty,name.If a cell(Say Code), is in edit mode, pressing the arrow or tab keys will fire the 'CellEndEdit' event and after that,moves the selection to the next cell. I want to have different selected cell if it is an arrow key and another selected if it is tab. eg:
On Right arrow key: Code -> Qty
On Tab press: Code -> Name
The datagridview's key events(down,up,press) dont fire once the cell enters edit mode.So, how can i get the last pressed key's value when the cell is in edit mode. I have to write the code/method/function in CellEndEdit event. Can this be something like:
private void DataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
//Some calculations;
//Get the key if it is tab or arrow to decide which cell should be selected next
If((bool)OnKeyDown()==true)
then do this;
}
void OnKeyDown(KeyEventArgs e)
{
if(e.KeyValue==9)//tab key
return true;
}
Using a solution from this link, I was able to come up with something that might be able to help you.
First you want to create your own user class which derives from DataGridView and override the ProcessCmdKey function. This function will get called regardless of whether or not your cell is in edit mode.
public partial class MyDataGridView : DataGridView
{
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == Keys.Tab)
{
this.CurrentCell = this.Rows[this.CurrentCell.RowIndex].Cells[1];
return true;
}
else if (keyData == Keys.Right)
{
this.CurrentCell = this.Rows[this.CurrentCell.RowIndex].Cells[2];
return true;
}
else
return base.ProcessCmdKey(ref msg, keyData);
}
}
All you need to do now is change the logic inside of this function to go to the appropriate column depending on the key which has been pressed, and use this custom class instead of the default DataGridView.
I resolved it this way based on a soln. here:
http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/0283fe97-c0ad-4768-8aff-cb4d14d48e15/
bool IsTabKey;
private void DataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
//Some calculations;
//Get the key if it is tab or arrow to decide which cell should be selected next
If(IsTabKey==true)
//then do this;
}
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if(GridViewSale.CurrentCell.ColumnIndex == 1 && keyData == Keys.Tab)
IsTabKey = true;
return false;
}
Related
I have a wpf application that has a datagrid with names in the first column and additional info in other columns. The names are in sorted order. If a user presses a key on the keyboard, say p, I would like the datagrid to go to the first row where the name begins with p. If the user then presses e, go to the first row that begins with pe, etc. Is this possible in a datagrid? I haven't been able to find anything or examples on this. Please help.
For that you should add keydown event.
And Step 1 : OnkeyDown event get text of key.
Step 2 : find item as per your condition from list.
Step 3 : Change selected item.
Step 4 : and scroll datagrid to selected item.
Window keydown event work correctly, in my case datagrid event worked when row was selected.
Here is code.
List<Employee> empData = new List<Employee>();
private Task task;
private CancellationToken token;
private CancellationTokenSource tokenSource;
private string searchText = "";
KeyDownEvent :
private void OnKeyDown(object sender, KeyEventArgs e)
{
if (task != null && tokenSource != null)
{
// cancel task
tokenSource.Cancel();
tokenSource = null;
Console.WriteLine("Task cancel");
}
// Set condition for key
string txt = new KeyConverter().ConvertToString(e.Key);
if (txt.ToString().ToList().Any(x => !Char.IsLetterOrDigit(x)))
{
Console.WriteLine("Retrun from.");
return;
}
searchText = searchText + new KeyConverter().ConvertToString(e.Key);
Console.WriteLine("Search text : " + searchText);
var item = empData.FirstOrDefault(x=>x.FirstName.StartsWith(searchText));
if (item != null)
{
myGrid.SelectedItem = item;
myGrid.UpdateLayout();
myGrid.ScrollIntoView(myGrid.SelectedItem);
}
// create task for clean text
Console.WriteLine("Task generate");
tokenSource = new CancellationTokenSource();
token = tokenSource.Token;
task = new Task(()=> CleanSearchText(token), token);
task.Start();
}
Task for clean text after sometime
private void CleanSearchText(CancellationToken token)
{
// Throw if cancellation request
token.ThrowIfCancellationRequested();
// Wait for sometime for next key prss
Thread.Sleep(400);
// Do nothing if cancelation request
if (token.IsCancellationRequested)
{
return;
}
Console.WriteLine("Clean text");
searchText = "";
}
It is possible. A simple example for Datagrid:
http://www.wpf-tutorial.com/datagrid-control/details-row/
Then handle event keydown in Datagrid
<DataGrid Name="dgUsers" AutoGenerateColumns="False" KeyDown="DgUsers_OnKeyDown">
code behind simple:
private void DgUsers_OnKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
foreach (var row in dgUsers.Items)
{
User user = (User) row;
user.Name = e.Key.ToString();
dgUsers.SelectedItem = row;
break;
}
}
This is already built in!
<DataGrid IsTextSearchEnabled="True" ...
Now just let the models "ToString" method return the text you want to search for - done!
See also https://msdn.microsoft.com/en-us/library/system.windows.controls.textsearch.aspx
I know this question is asked before, but I couldnt find what I am looking for.
private void dataGrid1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (oOrdItem.ItemNo == 0)
{
e.Handled = true;
MessageBox.Show("Please save the order item", "Save");
return;
}
}
Even if I call e.Handled = true;
it will select the datagrid row. I dont want to call dataGrid1.SelectedIndex =-1; because it will trigger selectionchanged event again. I also tried dataGrid1.UnSelectAll();
Any other way to cancel the selectionchanged event?
I used a variety of methods to try to cancel the selection changed event, including the method from the selected answer, but none of them worked. This, however, worked great for me:
Using the PreviewMouseDown event-handler for the datagrid:
private void dataGrid_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
//get the item I am clicking on (replace MyDataClass with datatype in datagrid)
var myItem = (e.OriginalSource as FrameworkElement).DataContext as MyDataClass;
//check if item is different from currently selected item
if (myItem != dataGrid.SelectedItem)
{
//save message dialog
MessageBoxResult result = MessageBox.Show("Changes will be lost. Are you sure?", "Confirmation", MessageBoxButton.YesNo, MessageBoxImage.Question);
//if click no, then cancel the event
if (result == MessageBoxResult.No)
{
e.Handled = true;
}
else
{
//otherwise, reinvoke the click event
dataGrid.Dispatcher.BeginInvoke(
new Action(() =>
{
RoutedEventArgs args = new MouseButtonEventArgs(e.MouseDevice, 0, e.ChangedButton);
args.RoutedEvent = UIElement.MouseDownEvent;
(e.OriginalSource as UIElement).RaiseEvent(args);
}),
System.Windows.Threading.DispatcherPriority.Input);
}
}
}
}
This successfully keeps the current row selected if the user clicks "No", and if they click "Yes", then execution will continue as normal. Hopefully this helps someone in the future, because it took a long time to find something that would work for a seemingly simple problem.
Did you think about an alternative implementation? I'm thinking on Binding and a check method before changing the SelectedItem. An illustration:
<DataGrid ItemsSource="..." SelectedItem="{Binding SelectedEntry}" />
and the underlying VM could look like this:
public class SampleVm : ViewModelBase//assuming that you are using such a base class
{
private object _selectedEntry;
public object SelectedEntry
{
get { return _selectedEntry; }
set
{
if (!SavePrevItem())
return;
_selectedEntry = value;
RaisePropertyChanged("SelectedItem"); // or something similar
}
}
private bool SavePrevItem()
{
// your logic here
}
}
I am looking to find a generic way to support keyboard wedge scanning for my WPF TextBox controls.
(I am really a novice when it comes to more advanced WPF features, so I would like to ask if I am going in the right direction before I put a lot of time into research.)
What I am wanting to do is to add an Attached Property (or something) to my TextBoxes that will cause it to read all input into the box and then call a custom "ScanCompleted" command with the scanned input.
If an Attached Property is not a good fit for this, then is there a way to get this command on a TextBox without descending my own custom "ScanableTextBox"?
(Note: The criteria for a scan (instead of typed data) is that it will start with the Pause key (#19) and end with a Return key (#13).)
I think this could probably be accomplished with attached properties (behaviors), but would be much simpler and more straightforward to simply subclass TextBox and override the OnTextChanged, OnKeyDown, OnKeyUp and similar methods to add custom functionality.
Why don't you want to create your own control in this way?
update: Attached Behaviour
If you really don't want a derived control, here is an attached behaviour that accomplishes this (explanation below):
public class ScanReading
{
private static readonly IDictionary<TextBox, ScanInfo> TrackedTextBoxes = new Dictionary<TextBox, ScanInfo>();
public static readonly DependencyProperty ScanCompletedCommandProperty =
DependencyProperty.RegisterAttached("ScanCompletedCommand", typeof (ICommand), typeof (ScanReading),
new PropertyMetadata(default(ICommand), OnScanCompletedCommandChanged));
public static void SetScanCompletedCommand(TextBox textBox, ICommand value)
{
textBox.SetValue(ScanCompletedCommandProperty, value);
}
public static ICommand GetScanCompletedCommand(TextBox textBox)
{
return (ICommand) textBox.GetValue(ScanCompletedCommandProperty);
}
private static void OnScanCompletedCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBox = d as TextBox;
if (textBox == null)
return;
var command = (ICommand) e.NewValue;
if (command == null)
{
textBox.Unloaded -= OnTextBoxUnloaded;
textBox.KeyUp -= OnTextBoxKeyUp;
TrackedTextBoxes.Remove(textBox);
}
else
{
textBox.Unloaded += OnTextBoxUnloaded;
TrackedTextBoxes.Add(textBox, new ScanInfo(command));
textBox.KeyUp += OnTextBoxKeyUp;
}
}
static void OnTextBoxKeyUp(object sender, KeyEventArgs e)
{
var textBox = (TextBox) sender;
var scanInfo = TrackedTextBoxes[textBox];
if (scanInfo.IsTracking)
{
if (e.Key == Key.Return)
{
scanInfo.ScanCompletedCommand.Execute(textBox.Text);
scanInfo.IsTracking = false;
}
}
else if (string.IsNullOrEmpty(textBox.Text) && e.Key == Key.Pause)
{
TrackedTextBoxes[textBox].IsTracking = true;
}
}
static void OnTextBoxUnloaded(object sender, RoutedEventArgs e)
{
var textBox = (TextBox) sender;
textBox.KeyUp -= OnTextBoxKeyUp;
textBox.Unloaded -= OnTextBoxUnloaded;
TrackedTextBoxes.Remove(textBox);
}
}
public class ScanInfo
{
public ScanInfo(ICommand scanCompletedCommand)
{
ScanCompletedCommand = scanCompletedCommand;
}
public bool IsTracking { get; set; }
public ICommand ScanCompletedCommand { get; private set; }
}
Consume this by declaring a TextBox like so (where local is the namespace of your attached property, and ScanCompleted is an ICommand on your view-model):
<TextBox local:ScanReading.ScanCompletedCommand="{Binding ScanCompleted}" />
Now when this property is set, we add the TextBox to a static collection along with its associated ICommand.
Each time a key is pressed, we check whether it is the Pause key. If it is, and if the TextBox is empty, we set a flag to true to start looking for the Enter key.
Now each time a key is pressed, we check whether it is the Enter key. If it is, we execute the command, passing in the TextBox.Text value, and reset the flag to false for that TextBox.
We've also added a handler for the TextBox.Unloaded event to clean up our event subscriptions and remove the TextBox from the static list.
I'm trying to get a rowindex of row at which I right clicked to call a contextmenu.
DatagridView's property contextmenu is set to this contextmenu.
Is it possible in some simple way?
Best regards
Yes, you need to handle the MouseDown event for your DataGridView and then use the HitTest method to return row and/or column index for the given coordinates.
For example:
private void dataGridView1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
DataGridView.HitTestInfo hit = dataGridView1.HitTest(e.X, e.Y);
if (hit.Type == DataGridViewHitTestType.Cell)
{
Console.WriteLine(hit.RowIndex);
}
}
}
I change the selection in the CellContextMenuStripNeeded event and then use the SelectedRows member to find it.
private void dataGridView_CellContextMenuStripNeeded(object sender, DataGridViewCellContextMenuStripNeededEventArgs e)
{
var Dgv = sender as DataGridView;
if (Dgv != null)
{
// Change the selection to reflect the right-click
Dgv.ClearSelection();
Dgv.Rows[e.RowIndex].Selected = true;
}
}
private void myToolStripMenuItem_Click(object sender, EventArgs e)
{
// Now pick up the selection as we know this is the row we right-clicked on
if (dataGridView.SelectedRows.Count > 0)
{
DoSomethingAmazing(dataGridView.SelectedRows[0]);
}
}
This also has the desired effect of highlighting a row that you r-click on.
i want to have a tooltip for each item in a treeview, and each item in a listview, and different for each subitem (i.e. column) in the listview.
i can determine the text i want to show (using hit testing with the current mouse position, etc):
private void toolTip1_Popup(object sender, PopupEventArgs e)
{
if (e.AssociatedControl == listView1)
{
toolTip1.SetToolTip(listView1, "foo");
}
}
but any attempt to set the tooltip text causes a stackoverflow.
How can i customize the tooltip (icon, title, text) just before it appears?
You need to guard your code in the Popup event handler so that if you are calling SetToolTip from within it, you don't call SetToolTip again.
Something like:
private bool updatingTooltip;
private void toolTip1_Popup(object sender, PopupEventArgs e)
{
if (!this.updatingTooltip && (e.AssociatedControl == listView1))
{
this.updatingTooltip = true;
toolTip1.SetToolTip(listView1, "foo");
this.updatingTooltip = false;
}
}