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;
..
}
}
Related
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();
}
}
}
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.
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 have two controls on the same form. Both controls contain an ObjectListView control. The one listview was created with the graphical editor in visual studio. This one is not causing any issues. The other listview is created programmatically at run-time. I have defined an event handler for each control that gets called when the hot item changes and they are both firing when they should. Both event handlers call the same code to update a picturebox control. The problem is that the picturebox does not get updated when the programmatically defined listview is asking it to. I am positive the event handler is getting called because my code writes to a text file as well as updating the picture box. The text file gets updated but the picture box does not. I have tried updating, invalidating, and refreshing the PicutureBox as well as the parent form, but I just can not get it to update.
I am not sure if this is an ObjectListView issue or a standard WinForms problem. I realize my question is very vague but I am not sure how to clarify it without posting all my code. Any advice would be appreciated.
Here is the code that the event handler calls:
public void ShowBitmap(object sender, HotItemChangedEventArgs e, ObjectListView lv, string type)
{
ObjectListView olv = sender as ObjectListView;
if (sender == null)
{
return;
}
switch (e.HotCellHitLocation)
{
case HitTestLocation.Nothing:
break;
case HitTestLocation.Group:
break;
case HitTestLocation.GroupExpander:
break;
default:
if (e.HotColumnIndex == 0)
{
pictureBox1.Hide();
pictureBox1.BorderStyle = BorderStyle.FixedSingle;
int rowIndex = e.HotRowIndex;
string text = "";
if (type == "Main Parts")
{
TypedObjectListView<MainRadanProjectPartsPart> tlist = new TypedObjectListView<MainRadanProjectPartsPart>(lv);
text = tlist.Objects[rowIndex].Symbol;
}
else if (type == "Parts")
{
TypedObjectListView<RadanProjectPartsPart> tlist = new TypedObjectListView<RadanProjectPartsPart>(lv);
text = tlist.Objects[rowIndex].Symbol;
}
else if (type == "Nests")
{
TypedObjectListView<MainRadanProjectNestsNest> tlist = new TypedObjectListView<MainRadanProjectNestsNest>(lv);
text = tlist.Objects[rowIndex].FileName;
}
if (text != null)
{
Point screenCoords = Cursor.Position;
Point controlRelatedCoords = lv.PointToClient(screenCoords);
if (controlRelatedCoords.Y < oldCursorPosition.Y)
{
pictureBox1.Location = controlRelatedCoords;
int xPos = controlRelatedCoords.X;
int yPos = controlRelatedCoords.Y + 60;
pictureBox1.Location = new Point(xPos, yPos);
}
else if (controlRelatedCoords.Y > oldCursorPosition.Y)
{
pictureBox1.Location = controlRelatedCoords;
int xPos = controlRelatedCoords.X;
//int yPos = controlRelatedCoords.Y - pictureBox1.Height;
int yPos = controlRelatedCoords.Y - pictureBox1.Height + 30;
pictureBox1.Location = new Point(xPos, yPos);
}
pictureBox1.Show();
pictureBox1.BringToFront();
olvTreeViewMainParts.Focus();
lv.Focus();
pictureBox1.Visible = true;
DrawSymbol(text);
oldCursorPosition = controlRelatedCoords; // save the cursor position to track cursor direction between calls
}
else
{
DrawSymbol("");
}
}
else
{
pictureBox1.Hide();
}
break;
}
}
Here is the event handler for the programmaticaly defined listview:
// track the cursor as it moves over the items in the listview
private void olvPartsListView_HotItemChanged(object sender, HotItemChangedEventArgs e)
{
ShowBitmap(sender, e, olvPartsListView, "Parts");
}
I have created a custom gridview control that inherits the asp.net gridview. I am required to use item templates in this gridview. I create a method in my custom gridview that generates the item template.
public void addTemplateField(Control headerTemplateControl, Control itemTemplateControl, EventHandler bindHandler, EventHandler initHandler, string headerText, string sortExpression, bool isVisible, int? heightPx, int? widthPercent)
{
TemplateField tField = new TemplateField();
if (headerTemplateControl != null)
tField.HeaderTemplate = new GridViewTemplate(ListItemType.Header, headerTemplateControl);
if (bindHandler != null && initHandler != null)
tField.ItemTemplate = new GridViewTemplate(ListItemType.Item, itemTemplateControl, bindHandler, initHandler);
else if (bindHandler != null)
tField.ItemTemplate = new GridViewTemplate(ListItemType.Item, itemTemplateControl, bindHandler, false);
else if (initHandler != null)
tField.ItemTemplate = new GridViewTemplate(ListItemType.Item, itemTemplateControl, initHandler, true);
else
tField.ItemTemplate = new GridViewTemplate(ListItemType.Item, itemTemplateControl);
if (sortExpression != null)
tField.SortExpression = sortExpression;
tField.Visible = isVisible;
if (headerText != null)
tField.HeaderText = headerText;
if (heightPx.HasValue)
tField.HeaderStyle.Height = new Unit(heightPx.Value, UnitType.Pixel);
if (widthPercent.HasValue)
tField.HeaderStyle.Height = new Unit(widthPercent.Value, UnitType.Percentage);
addColumnField(tField);
}
And this is how I have implemented ITemplate
public class GridViewTemplate : ITemplate
{
int _controlCount = 0;
ListItemType _templateType;
EventHandler _bindHandler;
EventHandler _initHandler;
Control _control;
public GridViewTemplate(ListItemType type, Control control)
{
this._templateType = type;
this._control = control;
}
public GridViewTemplate(ListItemType type, Control control, EventHandler Handler, bool isInitHandler)
{
this._templateType = type;
this._control = control;
if (isInitHandler)
this._initHandler = Handler;
else
this._bindHandler = Handler;
}
public GridViewTemplate(ListItemType type, Control control, EventHandler bindHandler, EventHandler initHandler)
{
this._templateType = type;
this._control = control;
this._bindHandler = bindHandler;
this._initHandler = initHandler;
}
public Control Copy(Control ctrlSource)
{
Type _type = ctrlSource.GetType();
Control ctrlDest = (Control)Activator.CreateInstance(_type);
foreach (PropertyInfo prop in _type.GetProperties())
{
if (prop.CanWrite)
{
if (prop.Name == "ID")
{
ctrlDest.ID = ctrlSource.ID + "_copy_" + _controlCount;
}
else
{
prop.SetValue(ctrlDest, prop.GetValue(ctrlSource, null), null);
}
}
}
_controlCount++;
return ctrlDest;
}
public void InstantiateIn(Control container)
{
switch (_templateType)
{
case ListItemType.Header:
container.Controls.Add(_control);
break;
case ListItemType.Item:
Control temp = Copy(_control);
if(_bindHandler != null)
temp.DataBinding += _bindHandler;
if (_initHandler != null)
temp.Init += _initHandler;
container.Controls.Add(temp);
break;
}
}
}
In the page that needs say Default.aspx.cs, I create this gridview onPreInit and attach its event handlers onInit.
I add a checkbox to the grid by calling the addTemplateField().
cbl = new CheckBox();
cbl.AutoPostBack = true;
init = new EventHandler(cbl_Init);
grd.addTemplateField(null, cbl, null, init, "SERVER", null, true, 20, 20);
void cbl_Init(object sender, EventArgs e)
{
CheckBox c = (CheckBox)sender;
c.CheckedChanged +=new EventHandler(cbl_CheckedChanged);
}
void cbl_CheckedChanged(object sender, EventArgs e)
{
// Modify datasource
// databind();
// if i remove this databind, checkchanged is handled every time. If i keep the databind, event is handled only alternate times.
}
The issue is the checkbox checkchanged event is fired for alternate times. Every other time, the page post backs but the checkchanged event is not handled. I am lost in finding the cause, let alone the solution.!?!?!
I found the root cause of the problem. It was in the Copy method of the gridviewtemplate class. The problem being for each postback, the controls generated were being done in a unique id. So on postback the event triggered by the control, had changed its id, so no event was triggered.
To be more crystal...
Page loads initially with controls having a unique id,
Click on the control to trigger an event
The page post backs with the controls being generated with the same id.
Click on the control to trigger the event.
The page posts back, but the controls are generated with a different it that does not match the event source of step 4.
Solution was to remove the control count variable.