How can I detect when the XAML Slider is Completed? - windows-8

In XAML I have the <Slider />. It has the ValueChanged event. This event fires with every change to Value. I need to detect when the value change is over. LostFocus, PointerReleased are not the correct event. How can I detect this?

XAML, WinRT, Windows8.1 and UWP:
PointerCaptureLost event should work for mouse / touch
KeyUp event for keyboard

You can create a new class and inherit from Slider. From there on, you can look for the Thumb control & listen for the events you want.
Something like this should work:
public class SliderValueChangeCompletedEventArgs : RoutedEventArgs
{
private readonly double _value;
public double Value { get { return _value; } }
public SliderValueChangeCompletedEventArgs(double value)
{
_value = value;
}
}
public delegate void SlideValueChangeCompletedEventHandler(object sender, SliderValueChangeCompletedEventArgs args);
public class ExtendedSlider : Slider
{
public event SlideValueChangeCompletedEventHandler ValueChangeCompleted;
private bool _dragging = false;
protected void OnValueChangeCompleted(double value)
{
if (ValueChangeCompleted != null)
{
ValueChangeCompleted(this, new SliderValueChangeCompletedEventArgs(value) );
}
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
var thumb = base.GetTemplateChild("HorizontalThumb") as Thumb;
if (thumb != null)
{
thumb.DragStarted += ThumbOnDragStarted;
thumb.DragCompleted += ThumbOnDragCompleted;
}
thumb = base.GetTemplateChild("VerticalThumb") as Thumb;
if (thumb != null)
{
thumb.DragStarted += ThumbOnDragStarted;
thumb.DragCompleted += ThumbOnDragCompleted;
}
}
private void ThumbOnDragCompleted(object sender, DragCompletedEventArgs e)
{
_dragging = false;
OnValueChangeCompleted(this.Value);
}
private void ThumbOnDragStarted(object sender, DragStartedEventArgs e)
{
_dragging = true;
}
protected override void OnValueChanged(double oldValue, double newValue)
{
base.OnValueChanged(oldValue, newValue);
if (!_dragging)
{
OnValueChangeCompleted(newValue);
}
}
}

You can use pair of bool values isValueChanged and (if possible change value without manipulation of pointer
) isPressed;
private void Slider_ValueChanged(object s, RangeBaseValueChangedEventArgs e) {
if (!isPressed) {
AcceptChanges();
} else {
isValueChanged = true;
}
}
Initialization code:
Window.Current.CoreWindow.PointerPressed += (e, a) => { isPressed = true; };
Window.Current.CoreWindow.PointerReleased += (e, a) => {
isPressed = false;
if (isValueChanged) AcceptChanges();
};

I had a similar issue using a Slider on Windows8/WinRT.
My problem was the following: I was reacting to the ValueChanged Event and performing a long lasting operation (writing asynchronously to a file) after each trigger. And thus running into a concurrent editing exception. In order to avoid this, I used a DispatcherTimer.
//Class member
private DispatcherTimer myDispatcherTimer = null;
private void OnSliderValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
//I update my UI right away
...
//If the dispatcher is already created, stop it
if (myDispatcherTimer!= null)
myDispatcherTimer.Stop();
//Overwrite the DispatcherTimer and thus reset the countdown
myDispatcherTimer= new DispatcherTimer();
myDispatcherTimer.Tick += (sender, o) => DoSomethingAsync();
myDispatcherTimer.Interval = new TimeSpan(0,0,2);
myDispatcherTimer.Start();
}
private async void DoSomethingAsync()
{
await DoThatLongSaveOperation();
}
You cannot directly detect what the final value is, but you can at least delay the operation until there is a long pause between two updates (e.g. in my case, if the user drags the slider and stops while maintaining the drag for 2 seconds, the save operation will be fired anyway).

Related

Revit external command with progress bar and 'Cancel' button

I am developing a new external command for Revit. it needs a progress bar + a button to cancel its execution in any moment.
In order to get it, I haver implemented a external event.
Implementing an external event handler with de code to be executed by the command.
public class GestorDeEventoExterno : IExternalEventHandler
{
public bool CancellationRequested { get; set; }
private VentanaDeProgreso progressAndcancelWindow;
private EventWaitHandle eventWait;
public void Execute(UIApplication aplicacionDeLaIU)
{
using (this.eventWait = new AutoResetEvent(false))
{
// New thread for the progress bar.
Thread progressBarThread = new Thread(new ThreadStart(() =>
{
// Populating the progress bar window.
this.progressAndcancelWindow = new VentanaDeProgreso(this);
progressAndcancelWindow.Show();
// Chenge the state of the wait event.
this.eventWait.Set();
Dispatcher.Run();
}));
progressBarThread.SetApartmentState(ApartmentState.STA);
progressBarThread.IsBackground = true;
progressBarThread.Start();
this.eventWait.WaitOne();
}
// Get the current revit document.
Document documentoActivo = aplicacionDeLaIU.ActiveUIDocument.Document;
// Code to simulate the revit command operation.
for (int i = 0;
i <= 100;
i++)
{
// Code to be executed if a cancellation has been requested.
if (this.CancellationRequested)
{
TaskDialog.Show("Test", "Cancel");
this.progressAndcancelWindow.Dispatcher.Invoke(new Action(this.progressAndcancelWindow.Close));
return;
}
this.progressAndcancelWindow.ActualizarProgreso($"loop number: {i}", i, 100);
Thread.Sleep(100);
}
this.progressAndcancelWindow.Dispatcher.Invoke(new Action(this.progressAndcancelWindow.Close));
TaskDialog.Show("Test", "END");
}
public string GetName()
{
return "test";
}
}
Implementing an external command to register the external event and populate the main window
public class Class1 : IExternalCommand
{
public Result Execute(
ExternalCommandData externalCommandData,
ref string message,
ElementSet elements)
{
// Registering the external event.
GestorDeEventoExterno externalEventHandler = new GestorDeEventoExterno();
ExternalEvent externalEvent = ExternalEvent.Create(externalEventHandler);
// Populating the main window.
VentanaPrincipal mainWindow = new VentanaPrincipal(
externalEvent);
mainWindow.Show();
return Result.Succeeded;
}
}
Finally, the code behind of the progress bar window
public partial class VentanaDeProgreso : Window
{
private GestorDeEventoExterno externalEventHandler;
public void ActualizarProgreso(
string texto,
int valorActual,
int valortotal = 100)
{
this.Dispatcher.Invoke(
new Action<string, int, int>(
delegate (string txt, int vActual, int vTotal)
{
this.IndicadorDeProgreso.Value = valorActual;
this.IndicadorDeProgreso.Maximum = vTotal;
this.Texto.Text = txt;
}),
System.Windows.Threading.DispatcherPriority.Background,
texto,
valorActual,
valortotal);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// Asignación de valor verdadero a la propiedad de cancelación solicitada del evento externo.
this.externalEventHandler.CancellationRequested = true;
}
public VentanaDeProgreso(GestorDeEventoExterno externalEventHandler)
{
InitializeComponent();
this.externalEventHandler= externalEventHandler;
}
}
AS you can see, the progress window has the external event handler as a property and the cancel button click event sets the property 'CancellationRequested'.
My question is: How can I improve it?
You do not need an external event to cancel your command.
You only need an external event to cancel submit a request to execute Revit API functionality from some context in which it is not available.
Your cancellation requires no Revit API functionality, just your own stuff, hence no external event.
Therefore, you can restructure the whole solution much more simply. Kiss!

Incorrect value using LINQ on DataGridView [duplicate]

I have a winforms app and want to trigger some code when a checkbox embedded in a DataGridView control is checked / unchecked. Every event I have tried either
Triggers as soon as the CheckBox is clicked but before its checked state changes, or
Triggers only once the CheckBox looses its focus
I can't seem to find event that triggers immediately after the checked state changes.
Edit:
What I am trying to achieve is that when the checked state of a CheckBox in one DataGridView changes, the data in two other DataGridViews changes. Yet all the events I have used, the data in the other grids only changes after the CheckBox in the first DataGridView looses focus.
To handle the DatGridViews CheckedChanged event you must first get the CellContentClick to fire (which does not have the CheckBoxes current state!) then call CommitEdit. This will in turn fire the CellValueChanged event which you can use to do your work. This is an oversight by Microsoft. Do some thing like the following...
private void dataGridViewSites_CellContentClick(object sender,
DataGridViewCellEventArgs e)
{
dataGridViewSites.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
/// <summary>
/// Works with the above.
/// </summary>
private void dataGridViewSites_CellValueChanged(object sender,
DataGridViewCellEventArgs e)
{
UpdateDataGridViewSite();
}
P.S. Check this article https://msdn.microsoft.com/en-us/library/system.windows.forms.datagridview.currentcelldirtystatechanged(v=vs.110).aspx
I found #Killercam's solution to work but was a bit dodgy if the user double clicked too fast. Not sure if other's found that the case either. I found a another solution here.
It uses the datagrid's CellValueChanged and CellMouseUp. Changhong explains that
"The reason for that is OnCellvalueChanged event won’t fire until the DataGridView thinks you have completed editing. This makes senses for a TextBox Column, as OnCellvalueChanged wouldn’t [bother] to fire for each key strike, but it doesn’t [make sense] for a CheckBox."
Here it is in action from his example:
private void myDataGrid_OnCellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == myCheckBoxColumn.Index && e.RowIndex != -1)
{
// Handle checkbox state change here
}
}
And the code to tell the checkbox it is done editing when it is clicked, instead of waiting till the user leaves the field:
private void myDataGrid_OnCellMouseUp(object sender,DataGridViewCellMouseEventArgs e)
{
// End of edition on each click on column of checkbox
if (e.ColumnIndex == myCheckBoxColumn.Index && e.RowIndex != -1)
{
myDataGrid.EndEdit();
}
}
Edit: A DoubleClick event is treated separate from a MouseUp event. If a DoubleClick event is detected, the application will ignore the first MouseUp event entirely. This logic needs to be added to the CellDoubleClick event in addition to the MouseUp event:
private void myDataGrid_OnCellDoubleClick(object sender,DataGridViewCellEventArgs e)
{
// End of edition on each click on column of checkbox
if (e.ColumnIndex == myCheckBoxColumn.Index && e.RowIndex != -1)
{
myDataGrid.EndEdit();
}
}
jsturtevants's solution worked great. However, I opted to do the processing in the EndEdit event. I prefer this approach (in my application) because, unlike the CellValueChanged event, the EndEdit event does not fire while you are populating the grid.
Here is my code (part of which is stolen from jsturtevant:
private void gridCategories_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == gridCategories.Columns["AddCategory"].Index)
{
//do some stuff
}
}
private void gridCategories_CellMouseUp(object sender, DataGridViewCellMouseEventArgs e)
{
if (e.ColumnIndex == gridCategories.Columns["AddCategory"].Index)
{
gridCategories.EndEdit();
}
}
Here is some code:
private void dgvStandingOrder_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
if (dgvStandingOrder.Columns[e.ColumnIndex].Name == "IsSelected" && dgvStandingOrder.CurrentCell is DataGridViewCheckBoxCell)
{
bool isChecked = (bool)dgvStandingOrder[e.ColumnIndex, e.RowIndex].EditedFormattedValue;
if (isChecked == false)
{
dgvStandingOrder.Rows[e.RowIndex].Cells["Status"].Value = "";
}
dgvStandingOrder.EndEdit();
}
}
private void dgvStandingOrder_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
dgvStandingOrder.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
private void dgvStandingOrder_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
if (dgvStandingOrder.CurrentCell is DataGridViewCheckBoxCell)
{
dgvStandingOrder.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
}
following Killercam'answer, My code
private void dgvProducts_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
dgvProducts.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
and :
private void dgvProducts_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (dgvProducts.DataSource != null)
{
if (dgvProducts.Rows[e.RowIndex].Cells[e.ColumnIndex].Value.ToString() == "True")
{
//do something
}
else
{
//do something
}
}
}
This also handles the keyboard activation.
private void dgvApps_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
if(dgvApps.CurrentCell.GetType() == typeof(DataGridViewCheckBoxCell))
{
if (dgvApps.CurrentCell.IsInEditMode)
{
if (dgvApps.IsCurrentCellDirty)
{
dgvApps.EndEdit();
}
}
}
}
private void dgvApps_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
// handle value changed.....
}
Ben Voigt found the best solution in a comment-reply above:
private void dgvStandingOrder_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
if (dgvStandingOrder.CurrentCell is DataGridViewCheckBoxCell)
dgvStandingOrder.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
Seriously, that's ALL you need.
What worked for me was CurrentCellDirtyStateChanged in combination with datagridView1.EndEdit()
private void dataGridView1_CurrentCellDirtyStateChanged( object sender, EventArgs e ) {
if ( dataGridView1.CurrentCell is DataGridViewCheckBoxCell ) {
DataGridViewCheckBoxCell cb = (DataGridViewCheckBoxCell)dataGridView1.CurrentCell;
if ( (byte)cb.Value == 1 ) {
dataGridView1.CurrentRow.Cells["time_loadedCol"].Value = DateTime.Now.ToString();
}
}
dataGridView1.EndEdit();
}
It's all about editing the cell, the problem that is the cell didn't edited actually, so you need to save The changes of the cell or the row to get the event when you click the check box so you can use this function:
datagridview.CommitEdit(DataGridViewDataErrorContexts.CurrentCellChange)
with this you can use it even with a different event.
I have found a simpler answer to this problem. I simply use reverse logic. The code is in VB but it is not much different than C#.
Private Sub DataGridView1_CellContentClick(sender As Object, e As
DataGridViewCellEventArgs) Handles DataGridView1.CellContentClick
Dim _ColumnIndex As Integer = e.ColumnIndex
Dim _RowIndex As Integer = e.RowIndex
'Uses reverse logic for current cell because checkbox checked occures
'after click
'If you know current state is False then logic dictates that a click
'event will set it true
'With these 2 check boxes only one can be true while both can be off
If DataGridView1.Rows(_RowIndex).Cells("Column2").Value = False And
DataGridView1.Rows(_RowIndex).Cells("Column3").Value = True Then
DataGridView1.Rows(_RowIndex).Cells("Column3").Value = False
End If
If DataGridView1.Rows(_RowIndex).Cells("Column3").Value = False And
DataGridView1.Rows(_RowIndex).Cells("Column2").Value = True Then
DataGridView1.Rows(_RowIndex).Cells("Column2").Value = False
End If
End Sub
One of the best things about this is no need for multiple events.
I've tried some answers from here, but I've always had some kind of problem (like double clicking or using the keyboard). So, I combined some of them and got a consistent behavior (it's not perfect, but works properly).
void gridView_CellContentClick(object sender, DataGridViewCellEventArgs e) {
if(gridView.CurrentCell.GetType() != typeof(DataGridViewCheckBoxCell))
return;
if(!gridView.CurrentCell.IsInEditMode)
return;
if(!gridView.IsCurrentCellDirty)
return;
gridView.EndEdit();
}
void gridView_CellMouseUp(object sender, DataGridViewCellMouseEventArgs e) {
if(e.ColumnIndex == gridView.Columns["cFlag"].Index && e.RowIndex >= 0)
gridView.EndEdit();
}
void gridView_CellValueChanged(object sender, DataGridViewCellEventArgs e) {
if(e.ColumnIndex != gridView.Columns["cFlag"].Index || e.RowIndex < 0)
return;
// Do your stuff here.
}
The Code will loop in DataGridView and Will check if CheckBox Column is Checked
private void dgv1_CellMouseUp(object sender, DataGridViewCellMouseEventArgs e)
{
if (e.ColumnIndex == 0 && e.RowIndex > -1)
{
dgv1.CommitEdit(DataGridViewDataErrorContexts.Commit);
var i = 0;
foreach (DataGridViewRow row in dgv1.Rows)
{
if (Convert.ToBoolean(row.Cells[0].Value))
{
i++;
}
}
//Enable Button1 if Checkbox is Checked
if (i > 0)
{
Button1.Enabled = true;
}
else
{
Button1.Enabled = false;
}
}
}
In the event CellContentClick you can use this strategy:
private void myDataGrid_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == 2)//set your checkbox column index instead of 2
{ //When you check
if (Convert.ToBoolean(myDataGrid.Rows[e.RowIndex].Cells[2].EditedFormattedValue) == true)
{
//EXAMPLE OF OTHER CODE
myDataGrid.Rows[e.RowIndex].Cells[5].Value = DateTime.Now.ToShortDateString();
//SET BY CODE THE CHECK BOX
myDataGrid.Rows[e.RowIndex].Cells[2].Value = 1;
}
else //When you decheck
{
myDataGrid.Rows[e.RowIndex].Cells[5].Value = String.Empty;
//SET BY CODE THE CHECK BOX
myDataGrid.Rows[e.RowIndex].Cells[2].Value = 0;
}
}
}
The best way that I found (which also doesn't use multiple events) is by handling the CurrentCellDirtyStateChanged event.
private void dataGrid_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
if (dataGridMatten.CurrentCell.OwningColumn == dataGridMatten.Columns["checkBoxColumn"] && dataGridMatten.IsCurrentCellDirty)
{
dataGrid.CommitEdit(DataGridViewDataErrorContexts.Commit);
//your code goes here
}
}
To do this when using the devexpress xtragrid, it is necessary to handle the EditValueChanged event of a corresponding repository item as described here. It is also important to call the gridView1.PostEditor() method to ensure the changed value has been posted. Here is an implementation:
private void RepositoryItemCheckEdit1_EditValueChanged(object sender, System.EventArgs e)
{
gridView3.PostEditor();
var isNoneOfTheAboveChecked = false;
for (int i = 0; i < gridView3.DataRowCount; i++)
{
if ((bool) (gridView3.GetRowCellValue(i, "NoneOfTheAbove")) && (bool) (gridView3.GetRowCellValue(i, "Answer")))
{
isNoneOfTheAboveChecked = true;
break;
}
}
if (isNoneOfTheAboveChecked)
{
for (int i = 0; i < gridView3.DataRowCount; i++)
{
if (!((bool)(gridView3.GetRowCellValue(i, "NoneOfTheAbove"))))
{
gridView3.SetRowCellValue(i, "Answer", false);
}
}
}
}
Note that because the xtragrid doesnt provide an enumerator it is necessary to use a for loop to iterate over rows.
Removing the focus after the cell value changes allow the values to update in the DataGridView. Remove the focus by setting the CurrentCell to null.
private void DataGridView1OnCellValueChanged(object sender, DataGridViewCellEventArgs dataGridViewCellEventArgs)
{
// Remove focus
dataGridView1.CurrentCell = null;
// Put in updates
Update();
}
private void DataGridView1OnCurrentCellDirtyStateChanged(object sender, EventArgs eventArgs)
{
if (dataGridView1.IsCurrentCellDirty)
{
dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
}
You can force the cell to commit the value as soon as you click the checkbox and then catch the CellValueChanged event. The CurrentCellDirtyStateChanged fires as soon as you click the checkbox.
The following code works for me:
private void grid_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
SendKeys.Send("{tab}");
}
You can then insert your code in the CellValueChanged event.
I use DataGridView with VirtualMode=true and only this option worked for me
(when both the mouse and the space bar are working, including repeated space clicks):
private void doublesGridView_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
var data_grid = (DataGridView)sender;
if (data_grid.CurrentCell.IsInEditMode && data_grid.IsCurrentCellDirty) {
data_grid.EndEdit();
}
}
private void doublesGridView_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == CHECKED_COLUMN_NUM && e.RowIndex >= 0 && e.RowIndex < view_objects.Count) { // view_objects - pseudocode
view_objects[e.RowIndex].marked = !view_objects[e.RowIndex].marked; // Invert the state of the displayed object
}
}
this worked for me
private void employeeDataGridView_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == employeeDataGridView.Columns["employeeStatusColumn"].Index)
{
bool isChecked = (bool)employeeDataGridView.CurrentCell.Value;
if (isChecked)
{
MessageBox.Show("Checked " + isChecked); //out true;
}
else
{
MessageBox.Show("unChecked " + isChecked);
}
}
}
private void employeeDataGridView_CellMouseUp(object sender, DataGridViewCellMouseEventArgs e)
{
if (employeeDataGridView.DataSource != null)
{
if (e.ColumnIndex == employeeDataGridView.Columns["employeeStatusColumn"].Index && e.RowIndex != -1)
{
employeeDataGridView.EndEdit();
}
}
}
private void dataGridViewPendingBill_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
bool isChecked = (bool) dataGridViewPendingBill[e.ColumnIndex, e.RowIndex].EditedFormattedValue;
if (isChecked)
{
totalAmount += int.Parse(dataGridViewPendingBill.Rows[e.RowIndex].Cells["Amount"].Value.ToString());
textBoxAmount.Text = totalAmount.ToString();
}
else
{
totalAmount -= int.Parse(dataGridViewPendingBill.Rows[e.RowIndex].Cells["Amount"].Value.ToString());
textBoxAmount.Text = totalAmount.ToString();
}
dataGridViewPendingBill.EndEdit();
}

How to SuggestAppend a ComboBox containing a string

Goal
I'd like to have my ComboBox items suggest and append its items when something is contained in them, not just via the StartsWith function.
My ComboBox is bound to a DataView which contains clients [CompanyName], [Address], [City] in a long concatenation.
I want my users to be able to type in the city and still find the records which matches with all of the fields above. I know this is possible with Infragistics but I don't have that package.
Search Term: "Sher"
Costco, 123 1st Avenue, Sherbrooke
Provigo, 344 Ball Street, Sherbrooke
Sherbox, 93 7th Street, Montreal
Is this possible in VB.Net or should I be searching for something else?
I did some research and found the following question:
Override Winforms ComboBox Autocomplete Suggest Rule
In that question they reffer to another question:
C# AutoComplete
Let's quote the best answer from that question
The existing AutoComplete functionality only supports searching by
prefix. There doesn't seem to be any decent way to override the
behavior.
Some people have implemented their own autocomplete functions by
overriding the OnTextChanged event. That's probably your best bet.
For example, you can add a ListBox just below the TextBox and set
its default visibility to false. Then you can use the OnTextChanged
event of the TextBox and the SelectedIndexChanged event of the
ListBox to display and select items.
This seems to work pretty well as a rudimentary example:
public Form1()
{
InitializeComponent();
acsc = new AutoCompleteStringCollection();
textBox1.AutoCompleteCustomSource = acsc;
textBox1.AutoCompleteMode = AutoCompleteMode.None;
textBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;
}
private void button1_Click(object sender, EventArgs e)
{
acsc.Add("[001] some kind of item");
acsc.Add("[002] some other item");
acsc.Add("[003] an orange");
acsc.Add("[004] i like pickles");
}
void textBox1_TextChanged(object sender, System.EventArgs e)
{
listBox1.Items.Clear();
if (textBox1.Text.Length == 0)
{
hideResults();
return;
}
foreach (String s in textBox1.AutoCompleteCustomSource)
{
if (s.Contains(textBox1.Text))
{
Console.WriteLine("Found text in: " + s);
listBox1.Items.Add(s);
listBox1.Visible = true;
}
}
}
void listBox1_SelectedIndexChanged(object sender, System.EventArgs e)
{
textBox1.Text = listBox1.Items[listBox1.SelectedIndex].ToString();
hideResults();
}
void listBox1_LostFocus(object sender, System.EventArgs e)
{
hideResults();
}
void hideResults()
{
listBox1.Visible = false;
}
There's a lot more you could do without too much effort: append text
to the text box, capture additional keyboard commands, and so forth.
Improved the technique demonstrated by BenD in his answer so as to have the mechanism handle a bit more elegantly certain cornercases:
public sealed class CCComboboxAutocomplete : ComboBox
{
public CCComboboxAutocomplete()
{
AutoCompleteMode = AutoCompleteMode.Suggest; //crucial otherwise exceptions occur when the user types in text which is not found in the autocompletion list
}
protected override void OnTextChanged(EventArgs e)
{
try
{
if (DesignMode || !string.IsNullOrEmpty(Text) || !Visible) return;
ResetCompletionList();
}
finally
{
base.OnTextChanged(e);
}
}
protected override void OnKeyPress(KeyPressEventArgs e)
{
try
{
if (DesignMode) return;
if (e.KeyChar == '\r' || e.KeyChar == '\n')
{
e.Handled = true;
if (SelectedIndex == -1 && Items.Count > 0 && Items[0].ToString().ToLowerInvariant().StartsWith(Text.ToLowerInvariant()))
{
Text = Items[0].ToString();
}
DroppedDown = false;
return; //0
}
BeginInvoke(new Action(ReevaluateCompletionList)); //1
}
finally
{
base.OnKeyPress(e);
}
}
//0 Guardclose when detecting any enter keypresses to avoid a glitch which was selecting an item by means of down arrow key followed by enter to wipe out the text within
//1 Its crucial that we use begininvoke because we need the changes to sink into the textfield Omitting begininvoke would cause the searchterm to lag behind by one character That is the last character that got typed in
private void ResetCompletionList()
{
_previousSearchterm = null;
try
{
SuspendLayout();
var originalList = (object[])Tag;
if (originalList == null)
{
Tag = originalList = Items.Cast<object>().ToArray();
}
if (Items.Count == originalList.Length) return;
while (Items.Count > 0)
{
Items.RemoveAt(0);
}
Items.AddRange(originalList);
}
finally
{
ResumeLayout(performLayout: true);
}
}
private string _previousSearchterm;
private void ReevaluateCompletionList()
{
var currentSearchterm = Text.ToLowerInvariant();
if (currentSearchterm == _previousSearchterm) return; //optimization
_previousSearchterm = currentSearchterm;
try
{
SuspendLayout();
var originalList = (object[])Tag;
if (originalList == null)
{
Tag = originalList = Items.Cast<object>().ToArray(); //0
}
var newList = (object[])null;
if (string.IsNullOrEmpty(currentSearchterm))
{
if (Items.Count == originalList.Length) return;
newList = originalList;
}
else
{
newList = originalList.Where(x => x.ToString().ToLowerInvariant().Contains(currentSearchterm)).ToArray();
}
try
{
while (Items.Count > 0) //1
{
Items.RemoveAt(0);
}
}
catch
{
try
{
Items.Clear();
}
catch
{
}
}
Items.AddRange(newList.ToArray()); //2
}
finally
{
if (currentSearchterm.Length >= 2 && !DroppedDown)
{
DroppedDown = true; //3
Cursor.Current = Cursors.Default; //4
Text = currentSearchterm; //5
Select(currentSearchterm.Length, 0);
}
ResumeLayout(performLayout: true);
}
}
//0 backup original list
//1 clear list by loop through it otherwise the cursor would move to the beginning of the textbox
//2 reset list
//3 if the current searchterm is empty we leave the dropdown list to whatever state it already had
//4 workaround for the fact the cursor disappears due to droppeddown=true This is a known bu.g plaguing combobox which microsoft denies to fix for years now
//5 Another workaround for a glitch which causes all text to be selected when there is a matching entry which starts with the exact text being typed in
}
Sorry for another answer in C# but I have a more improved answer based on xDisruptor's code.
Using kinda behavior (decorator).
You don't have to subclass ComboBox and change all existing combos in the designed.
Be careful when using Datasource instead of Items collection, because it'll raise an exception.
Code:
public class AutoCompleteBehavior
{
private readonly ComboBox comboBox;
private string previousSearchterm;
private object[] originalList;
public AutoCompleteBehavior(ComboBox comboBox)
{
this.comboBox = comboBox;
this.comboBox.AutoCompleteMode = AutoCompleteMode.Suggest; // crucial otherwise exceptions occur when the user types in text which is not found in the autocompletion list
this.comboBox.TextChanged += this.OnTextChanged;
this.comboBox.KeyPress += this.OnKeyPress;
this.comboBox.SelectionChangeCommitted += this.OnSelectionChangeCommitted;
}
private void OnSelectionChangeCommitted(object sender, EventArgs e)
{
if (this.comboBox.SelectedItem == null)
{
return;
}
var sel = this.comboBox.SelectedItem;
this.ResetCompletionList();
this.comboBox.SelectedItem = sel;
}
private void OnTextChanged(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(this.comboBox.Text) || !this.comboBox.Visible || !this.comboBox.Enabled)
{
return;
}
this.ResetCompletionList();
}
private void OnKeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == '\r' || e.KeyChar == '\n')
{
e.Handled = true;
if (this.comboBox.SelectedIndex == -1 && this.comboBox.Items.Count > 0
&& this.comboBox.Items[0].ToString().ToLowerInvariant().StartsWith(this.comboBox.Text.ToLowerInvariant()))
{
this.comboBox.Text = this.comboBox.Items[0].ToString();
}
this.comboBox.DroppedDown = false;
// Guardclause when detecting any enter keypresses to avoid a glitch which was selecting an item by means of down arrow key followed by enter to wipe out the text within
return;
}
// Its crucial that we use begininvoke because we need the changes to sink into the textfield Omitting begininvoke would cause the searchterm to lag behind by one character That is the last character that got typed in
this.comboBox.BeginInvoke(new Action(this.ReevaluateCompletionList));
}
private void ResetCompletionList()
{
this.previousSearchterm = null;
try
{
this.comboBox.SuspendLayout();
if (this.originalList == null)
{
this.originalList = this.comboBox.Items.Cast<object>().ToArray();
}
if (this.comboBox.Items.Count == this.originalList.Length)
{
return;
}
while (this.comboBox.Items.Count > 0)
{
this.comboBox.Items.RemoveAt(0);
}
this.comboBox.Items.AddRange(this.originalList);
}
finally
{
this.comboBox.ResumeLayout(true);
}
}
private void ReevaluateCompletionList()
{
var currentSearchterm = this.comboBox.Text.ToLowerInvariant();
if (currentSearchterm == this.previousSearchterm)
{
return;
}
this.previousSearchterm = currentSearchterm;
try
{
this.comboBox.SuspendLayout();
if (this.originalList == null)
{
this.originalList = this.comboBox.Items.Cast<object>().ToArray(); // backup original list
}
object[] newList;
if (string.IsNullOrEmpty(currentSearchterm))
{
if (this.comboBox.Items.Count == this.originalList.Length)
{
return;
}
newList = this.originalList;
}
else
{
newList = this.originalList.Where(x => x.ToString().ToLowerInvariant().Contains(currentSearchterm)).ToArray();
}
try
{
// clear list by loop through it otherwise the cursor would move to the beginning of the textbox
while (this.comboBox.Items.Count > 0)
{
this.comboBox.Items.RemoveAt(0);
}
}
catch
{
try
{
this.comboBox.Items.Clear();
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
this.comboBox.Items.AddRange(newList.ToArray()); // reset list
}
finally
{
if (currentSearchterm.Length >= 1 && !this.comboBox.DroppedDown)
{
this.comboBox.DroppedDown = true; // if the current searchterm is empty we leave the dropdown list to whatever state it already had
Cursor.Current = Cursors.Default; // workaround for the fact the cursor disappears due to droppeddown=true This is a known bu.g plaguing combobox which microsoft denies to fix for years now
this.comboBox.Text = currentSearchterm; // Another workaround for a glitch which causes all text to be selected when there is a matching entry which starts with the exact text being typed in
this.comboBox.Select(currentSearchterm.Length, 0);
}
this.comboBox.ResumeLayout(true);
}
}
}
Usege:
new AutoCompleteBehavior(this.comboBoxItems);
this.comboBoxItems.Items.AddRange(new object[] { "John", "Tina", "Doctor", "Alaska" });
TIP: Can be further improved by making an extension to the ComboBox class like myCombo.ToAutoComplete()
A ComboBox,TextBox and I think a DropDownList has AutoComplete properties
Look at http://msdn.microsoft.com/en-us/library/system.windows.forms.combobox.autocompletemode(v=vs.110).aspx
It explains which AutoCompleteMode you should use and how to set the AutoCompleteSource
You could try the following lines, it worked for me
cbxName.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
cbxName.AutoCompleteSource = AutoCompleteSource.ListItems;

CompositionTarget.Rendering doesn't like my event handler in XAML

I'm converting a Windows Phone 7 app to Windows Store, so I'm moving over to Xaml. I have a method that runs at a certain point to update the data on the screen. It either assigns or removes an event handler delegate to the CompositionTarget.Rendering event. The message I get is No overload for 'OnCompositionTargetRendering' matches delegate 'System.EventHandler' '
Here's what I have:
private void CheckCompleted()
{
Color completeColor;
if (this.DecryptedText.ToString().ToUpper() == this.ThisPuzzle.QuoteText.ToUpper())
{
// We're done!!! ...
CompositionTarget.Rendering -= this.OnCompositionTargetRendering;// new EventHandler(this.OnCompositionTargetRendering);
...
}
else
{
...
CompositionTarget.Rendering += this.OnCompositionTargetRendering;// new EventHandler(this.OnCompositionTargetRendering);
...
}
}
protected void OnCompositionTargetRendering(object sender, EventArgs args)
{
this.DisplayTime();
if (ThisPuzzle != null)
{
foreach (UIElement thisElement in Letters.Children)
{
...
}
}
}
If you check the documentation CompositionTarget.Rendering is of type EventHandler<object> in Windows Store apps and not of type EventHandler as in Silverlight.
This means you need to change the signature of your event handler accordingly to:
protected void OnCompositionTargetRendering(object sender, object args)

How do I hide a PivotItem?

I have a Page with an Pivot-control and in some cases I don't want to show a particular PivotItem.
Setting the Visibility to collapsed doesn't seem to affect it at all.
Any suggestions?
you should be able to remove or add PivotItems dynamically in your Pivot by using the respective collection methods on Pivot.Items .
Let me know if this doesn't work for your scenario.
I've created a custom behavior for showing/hiding pivot item
Usage:
< i:Interaction.Behaviors>
< common:HideablePivotItemBehavior Visible="{Binding variable}" />
</ i:Interaction.Behaviors >
Code:
/// <summary>
/// Behavior which enables showing/hiding of a pivot item`
/// </summary>
public class HideablePivotItemBehavior : Behavior<PivotItem>
{
#region Static Fields
public static readonly DependencyProperty VisibleProperty = DependencyProperty.Register(
"Visible",
typeof(bool),
typeof(HideablePivotItemBehavior),
new PropertyMetadata(true, VisiblePropertyChanged));
#endregion
#region Fields
private Pivot _parentPivot;
private PivotItem _pivotItem;
private int _previousPivotItemIndex;
private int _lastPivotItemsCount;
#endregion
#region Public Properties
public bool Visible
{
get
{
return (bool)this.GetValue(VisibleProperty);
}
set
{
this.SetValue(VisibleProperty, value);
}
}
#endregion
#region Methods
protected override void OnAttached()
{
base.OnAttached();
this._pivotItem = AssociatedObject;
}
private static void VisiblePropertyChanged(DependencyObject dpObj, DependencyPropertyChangedEventArgs change)
{
if (change.NewValue.GetType() != typeof(bool) || dpObj.GetType() != typeof(HideablePivotItemBehavior))
{
return;
}
var behavior = (HideablePivotItemBehavior)dpObj;
var pivotItem = behavior._pivotItem;
// Parent pivot has to be assigned after the visual tree is initialized
if (behavior._parentPivot == null)
{
behavior._parentPivot = (Pivot)behavior._pivotItem.Parent;
// if the parent is null return
if (behavior._parentPivot == null)
{
return;
}
}
var parentPivot = behavior._parentPivot;
if (!(bool)change.NewValue)
{
if (parentPivot.Items.Contains(behavior._pivotItem))
{
behavior._previousPivotItemIndex = parentPivot.Items.IndexOf(pivotItem);
parentPivot.Items.Remove(pivotItem);
behavior._lastPivotItemsCount = parentPivot.Items.Count;
}
}
else
{
if (!parentPivot.Items.Contains(pivotItem))
{
if (behavior._lastPivotItemsCount >= parentPivot.Items.Count)
{
parentPivot.Items.Insert(behavior._previousPivotItemIndex, pivotItem);
}
else
{
parentPivot.Items.Add(pivotItem);
}
}
}
}
#endregion
}
You can remove the pivot item from the parent pivot control
parentPivotControl.Items.Remove(pivotItemToBeRemoved);
Removing PivotItems is easy, but if you want to put them back afterwards I've found that the headers get messed up and start overlapping each other. This also happens if you set the Visibility of a header to Collapsed and then later make it Visible again.
So I solved my particular problem by setting the opacity of each unwanted PivotItem (and its header) to 0.
PivotItem p = (PivotItem)MainPivot.Items.ToList()[indexToHide];
p.Opacity = 0;
((UIElement)p.Header).Opacity = 0;
However, this leaves gaps where the missing PivotItems are.
For me, the gaps were not a problem because I only want to remove items at the end of my PivotItemList, so I get some whitespace between the last and first PivotItems. The problem was, I was still able to swipe to a hidden PivotItem. In order to fix this, I overrode Pivot.SelectionChanged() so that whenever the user swipes to a hidden PivotItem, the code moves on to the next item instead. I had to use a DispatchTimer from within SelectionChanged() and actually move to the next PivotItem from the DispatchTimer callback, since you have to be in the UI thread to change PivotItem.SelectedIndex.
private void MainPivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
t.Stop(); //t is my DispatchTimer, set to 100ms
if (MainPivot.SelectedIndex >= mFirstHiddenPivotItemIndex)
{
//move to the first or last PivotItem, depending on the current index
if (mCurrentSelectedPivotItemIndex == 0)
mPivotItemToMoveTo = mFirstHiddenPivotItemIndex - 1;
else
mPivotItemToMoveTo = 0;
t.Start();
}
mCurrentSelectedPivotItemIndex = MainPivot.SelectedIndex;
}
private void dispatcherTimer_Tick(object sender, EventArgs e)
{
MainPivot.SelectedIndex = mPivotItemToMoveTo;
t.Stop();
}
foreach (PivotItem item in MyPivot.Items.ToList())
{
if (item.Visibility == Visibility.Collapsed)
MyPivot.Items.Remove(item);
}
Setting IsLocked property to true will make all other Pivot items to disappear except the current pivot item.
But this will not hide one particular pivot item of our choice.
To elaborate on the solution of adding/removing pivotItems, rather than hiding them.
Let's say we want the pivotItem to be initially invisible, and appear only on a certain event.
mainPivot.Items.Remove(someTab);
Then to add it again,
if (!mainPivot.Items.Cast<PivotItem>().Any(p => p.Name == "someTab"))
{
mainPivot.Items.Insert(1,someTab);
}
I've used Insert rather than add to control the position where the tab appears.
You have to ensure you don't add the same tab twice, which is the reason for the if statement.
I've modified the Bajena behavior to improve it, solving the issue with losing the original position of the PivotItem when showing/hiding repeteadly and the issue when parentpivot control is null (not initialized yet).
Notice that this behavior must be attached to the Pivot, not to the PivotItem.
Code:
public class PivotItemHideableBehavior : Behavior<Pivot>
{
private Dictionary<PivotItem, int> DictionaryIndexes { get; set; }
public static readonly DependencyProperty VisibleProperty = DependencyProperty.Register(
"Visible",
typeof(bool),
typeof(PivotItemHideableBehavior),
new PropertyMetadata(true, VisiblePropertyChanged));
public static readonly DependencyProperty PivotItemProperty = DependencyProperty.Register(
"PivotItem",
typeof(PivotItem),
typeof(PivotItemHideableBehavior),
new PropertyMetadata(null));
public bool Visible
{
get { return (bool)GetValue(VisibleProperty); }
set { SetValue(VisibleProperty, value); }
}
public PivotItem PivotItem
{
get { return (PivotItem)GetValue(PivotItemProperty); }
set { SetValue(PivotItemProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += AssociatedObject_Loaded;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Loaded -= AssociatedObject_Loaded;
}
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
DictionaryIndexes = new Dictionary<PivotItem, int>();
int index = 0;
foreach (PivotItem item in AssociatedObject.Items)
DictionaryIndexes.Add(item, index++);
}
private static void VisiblePropertyChanged(DependencyObject dpObj, DependencyPropertyChangedEventArgs change)
{
var behavior = (PivotItemHideableBehavior)dpObj;
var pivot = behavior.AssociatedObject;
if (!behavior.Visible)
{
if (pivot.Items.Contains(behavior.PivotItem))
pivot.Items.Remove(behavior.PivotItem);
}
else if (!pivot.Items.Contains(behavior.PivotItem))
{
int index = 0;
foreach (var item in behavior.DictionaryIndexes)
{
if (item.Key == behavior.PivotItem)
pivot.Items.Insert(index, behavior.PivotItem);
else if (pivot.Items.Contains(item.Key))
index++;
}
}
}
}
Usage:
<Interactivity:Interaction.Behaviors>
<Behaviors:PivotItemHideableBehavior PivotItem="{x:Bind PivotItemName}" Visible="{Binding IsPivotItemVisible}" />
</Interactivity:Interaction.Behaviors>