Allowing 'pasting data' into a WPF textbox - c#

I'm trying to intercept data pasted into a WPF textbox.
For example, the user creates a screen capture with the Windows snipping tool,
which automatically places the image data on the clipboard. The idea here is to
allow the user to simply CTRL+V on the TextBox so that I can intercept it, check if it's
data and then do whatever I want with it.
public class PasteBehavior : Behavior<UIElement>
{
protected override void OnAttached()
{
base.OnAttached();
DataObject.AddPastingHandler(AssociatedObject, new DataObjectPastingEventHandler(OnPaste));
}
protected override void OnDetaching()
{
base.OnDetaching();
}
private void OnPaste(object sender, DataObjectPastingEventArgs e)
{
if (e.SourceDataObject.GetDataPresent(DataFormats.Text))
return;
var formats = e.SourceDataObject.GetFormats();
foreach (var format in formats)
Console.WriteLine(format);
}
}
Using the behavior above, the code does get triggered when text is pasted into the TextBox
but it would seem the TextBox does not allow anything else to be pasted so it never even reaches this code if it's not text.
I'm wondering, is there a property that needs to be set on the TextBox, or something else
that would allow data to be pasted (even though the TextBox can never display that data)
If not, what UI elements do allow data to be pasted, as I might be able to use that to my advantage as well.
Update
Someone posted out to me that I'd have to use a RichTextBox to allow pasting
like this, which is not something I can use, so I decided to take a different (somewhat hacky) approach:
public class PasteBehavior : Behavior<UIElement>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewKeyDown += AssociatedObject_PreviewKeyDown;
}
void AssociatedObject_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (Keyboard.Modifiers == ModifierKeys.Control && e.Key == Key.V)
{
if (Clipboard.ContainsData(DataFormats.Dib))
{
using (var stream = new MemoryStream())
{
var image = Clipboard.GetImage();
var message = new ImagePastedMessage()
{
ImageData = GetImagePngData(image)
};
Messenger.Default.Send(message);
}
e.Handled = true;
}
else if (Clipboard.ContainsFileDropList())
{
var results = Clipboard.GetFileDropList();
var filenames = new string[results.Count];
results.CopyTo(filenames, 0);
var message = new FilesDroppedMessage()
{
Filenames = filenames
};
Messenger.Default.Send(message);
e.Handled = true;
}
}
}
protected override void OnDetaching()
{
base.OnDetaching();
}
private byte[] GetImagePngData(BitmapSource source)
{
using (var stream = new MemoryStream())
{
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(source));
encoder.Save(stream);
return stream.ToArray();
}
}
}
This allows me to paste images and files into the TextBox but only
using the CTRL+V keys, not using the default context menu of the TextBox.
So I'm still interested in knowing if there is a better/easier way
Update 2
Based on the solution by Daniel, which works really well, I've updated the OnAttached:
protected override void OnAttached()
{
base.OnAttached();
CommandManager.AddPreviewCanExecuteHandler(AssociatedObject, onPreviewCanExecute);
CommandManager.AddPreviewExecutedHandler(AssociatedObject, onPreviewExecuted);
}
And removed the PreviewKeyDownHandler.

You can use CommandManager.PreviewExecuted and CommandManager.PreviewCanExecute routed events to handle your pasting logic.
For example, let's suppose you want to accept an image from the clipboard when a user tries to paste it into your TextBox. So first, define the methods that will handle both events:
private void onPreviewCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
// In this case, we just say it always can be executed (only for a Paste command), but you can
// write some checks here
if (e.Command == ApplicationCommands.Paste)
{
e.CanExecute = true;
e.Handled = true;
}
}
private void onPreviewExecuted(object sender, ExecutedRoutedEventArgs e)
{
// If it is a paste command..
if (e.Command == ApplicationCommands.Paste)
{
// .. and the clipboard contains an image
if (Clipboard.ContainsImage())
{
// proccess it somehow
e.Handled = true;
}
}
}
Then, you have to associate those methods with the routed events (this could go in the constructor, for example):
CommandManager.AddPreviewExecutedHandler(myTextBox, onPreviewExecuted);
CommandManager.AddPreviewCanExecuteHandler(myTextBox, onPreviewCanExecute);
And it should work with both the keyboard shortcut and the menu 'button'.
It is important to handle the PreviewCanExecute event. By default, the TextBox will only accept text as a 'pasteable' content, so you need to mark that content somehow in order to paste it.
EDIT:
Also, it is a good practice to remove the 'listeners' from the event if you can. As you're using behaviors, you can do this by overriding the 'OnDetaching' method in your behavior. This could prevent memory leaks if the events are not Weak Events:
protected override void OnDetaching()
{
base.OnDetaching();
CommandManager.RemovePreviewExecutedHandler(myTextBox, onPreviewExecuted);
CommandManager.RemovePreviewCanExecuteHandler(myTextBox, onPreviewCanExecute);
}

Related

Is it possible to tell if the Text change on an entry was from code or from the UI?

I am working on a Xamarin project and I need to be able to tell if the changes that occur to the text in an Entry view are from the code or from the UI, is this possible in Xamarin? or is there a known work around to do this.
I know about the OnTextChanged event but this only tells you that the Text property has changed, and gives you access to the old and new value of the Text property. It does not differentiate between different causes of text change.
You can get some idea from this thread, check if the entry is focused to differentiate between different causes of text change:
public MainPage()
{
InitializeComponent();
myEntry.TextChanged += MyEntry_TextChanged;
}
private void MyEntry_TextChanged(object sender, TextChangedEventArgs e)
{
var entry = sender as Entry;
if (entry.IsFocused)
{
//change from UI
Console.WriteLine("change from UI");
}
else{
//change from code
Console.WriteLine("change from code");
}
}
Update: The better way to solve op's problem:
You can set a flag yourself that tells your code to ignore the event. For example:
private bool ignoreTextChanged;
private void textNazwa_TextCanged(object sender, EventArgs e)
{
if (ignoreTextChanged) return;
}
Create a method and use this to set the text instead of just calling Text = "...";::
private void SetTextBoxText(TextBox box, string text)
{
ignoreTextChanged = true;
box.Text = text;
ignoreTextChanged = false;
}
Refer: ignoreTextChanged
you can use EntryRenderer to detect keypress event and use that flag to detect the change by code or by UI.
Here are the step:
- Exetend your entry control with new event OnTextChangeByUI
- Write custom render for both platform
e.g for android it will be something like this
public class ExtendedEntryRender : EntryRenderer
{
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (Control != null)
{
Control.KeyPress += ((Entry)Element).OnTextChangeByUI;
}
}
}

C# editable listview

Yesterday I try to implement a new listview that support sub-item edit, my solution is to show a textbox when double click the sub-item. The key code as following:
protected override void OnDoubleClick(EventArgs e)
{
Point pt = this.PointToClient(Cursor.Position);
ListViewItem curItem;
int subItemIndex = GetSubItemAt(pt.X, pt.Y, out curItem);
DoubleClickEventArgs args = new DoubleClickEventArgs(subItemIndex);
base.OnDoubleClick(args);
if (subItemIndex>=0 && !args.Cancel)
{
//StartEdit(...);
}
}
public void EndEdit(bool acceptChanges)
{
//validation
.................
.................
AfterSubItemEventArgs e = new AfterSubItemEventArgs(this.SelectedItems[0], m_editSubItemIndex, this.SelectedItems[0].SubItems[m_editSubItemIndex].Text, m_textbox.Text, false);
OnAfterSubItemEdit(e);
if (e.Cancel)
{
//....
}
else
{
//set new value
}
m_textbox.Visible = false;
m_editSubItemIndex = -1;
}
OnAfterSubItemEdit is a event that user can do some validations or other operations. I add a check in this method, if the new value exist, I will show a messagebox to user firstly, then hide the textbox. But now, the problem comes, when i move the mouse, the listview items can be selected, I don't how to solve this issue, I tried my best to find out the way, but failed. So, please help me!
Listview has a LabelEdit property; when you set it "true", then in an event handler you can call Listview.Items[x].BeginEdit(), and edit an item. As an example, you can handle ListView.DoubleClick event and call BeginEdit right there:
private void Form1_Load(object sender, System.EventArgs e)
{
listView1.LabelEdit = true;
}
private void listView1_DoubleClick(object sender, System.EventArgs e)
{
if(this.listView1.SelectedItems.Count==1)
{
this.listView1.SelectedItems[0].BeginEdit();
}
}
The problem is that your form still calls the DoubleClick event whether the value exists or not. Add appropriate condition before calling base DoubleClick in your code, i.e.:
if(!new value exists)
base.OnDoubleClick(args);

In C# clear a UserControl on Unload event

I have a System.Web.UI.UserControl in my application which is to be used to display messages to the user, however after these messages are displayed to the user once I want them to clear (conditionally).
The simplified code I have now that I am trying to get to work is the following:
protected override void OnUnload(EventArgs e) {
if (_resetOnUnload) {
divMessageBlock.InnerHtml = "";
_resetOnUnload = false;
}
base.OnUnload(e);
}
However any changes to the view in the OnUnload event do not get transferred over on the next page load (form submit).
My question is how would I setup this user control to clear itself either before any messages can be added elsewhere or after the page has been rendered for the user and have stay that way?
Try this. Set the viewstate to save _resetOnUnload and then load it. OnLoad can stay the same, testing whether or not to clear.
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
if (_resetOnUnload) {
divMessageBlock.InnerHtml = "";
_resetOnUnload = false;
}
}
protected override void LoadViewState(object savedState) {
if (savedState != null) {
object[] myState = (object[])savedState;
_resetOnUnload = (bool)myState[0];
}
}
protected override object SaveViewState() {
object[] allStates = new object[]{ _resetOnUnload };
return allStates;
}
How about just outputting your message to a control with ViewState turned off? That way, your message will be displayed, and any subsequent Postback will clear the message.
I guess the OnPreRender event is the last one, where you can change what the control's output should be. After that, the control has been rendered, and your changes will have no effect.

C# wait for user to finish typing in a Text Box

Is there a way in C# to wait till the user finished typing in a textbox before taking in values they have typed without hitting enter?
Revised this question a little:
Okay I have a simple calculator that multiplies by 2.
Here is what I want it to do: The user inputs a value like 1000 into a textbox and it automatically displays 2000.
Here is what happens: As soon as the user enters in 1 its multiplies by 2 and outputs 2.
I define "finished typing" now as "user has typed something but has not typed anything after a certain time". Having that as a definition i wrote a little class that derives from TextBox to extend it by a DelayedTextChanged event. I do not ensure that is complete and bug free but it satisfied a small smoke test. Feel free to change and/or use it. I called it MyTextBox cause i could not come up with a better name right now. You may use the DelayedTextChangedTimeout property to change the wait timeout. Default is 10000ms (= 10 seconds).
public class MyTextBox : TextBox
{
private Timer m_delayedTextChangedTimer;
public event EventHandler DelayedTextChanged;
public MyTextBox() : base()
{
this.DelayedTextChangedTimeout = 10 * 1000; // 10 seconds
}
protected override void Dispose(bool disposing)
{
if (m_delayedTextChangedTimer != null)
{
m_delayedTextChangedTimer.Stop();
if (disposing)
m_delayedTextChangedTimer.Dispose();
}
base.Dispose(disposing);
}
public int DelayedTextChangedTimeout { get; set; }
protected virtual void OnDelayedTextChanged(EventArgs e)
{
if (this.DelayedTextChanged != null)
this.DelayedTextChanged(this, e);
}
protected override void OnTextChanged(EventArgs e)
{
this.InitializeDelayedTextChangedEvent();
base.OnTextChanged(e);
}
private void InitializeDelayedTextChangedEvent()
{
if (m_delayedTextChangedTimer != null)
m_delayedTextChangedTimer.Stop();
if (m_delayedTextChangedTimer == null || m_delayedTextChangedTimer.Interval != this.DelayedTextChangedTimeout)
{
m_delayedTextChangedTimer = new Timer();
m_delayedTextChangedTimer.Tick += new EventHandler(HandleDelayedTextChangedTimerTick);
m_delayedTextChangedTimer.Interval = this.DelayedTextChangedTimeout;
}
m_delayedTextChangedTimer.Start();
}
private void HandleDelayedTextChangedTimerTick(object sender, EventArgs e)
{
Timer timer = sender as Timer;
timer.Stop();
this.OnDelayedTextChanged(EventArgs.Empty);
}
}
Another simple solution would be to add a timer to your form, set the Interval property to 250 and then use the timer's tick event as follows:
private void timer1_Tick(object sender, EventArgs e)
{
timer1.Stop();
Calculate(); // method to calculate value
}
private void txtNumber_TextChanged(object sender, EventArgs e)
{
timer1.Stop();
timer1.Start();
}
If you are using WPF and .NET 4.5 or later there is a new property on the Binding part of a control named "Delay". It defines a timespan after which the source is updated.
<TextBox Text="{Binding Name, Delay=500}" />
This means the source is updated only after 500 milliseconds. As far as I see it it does the update after typing in the TextBox ended. Btw. this property can be usefull in other scenarios as well, eg. ListBox etc.
I faced the same challenge, and here is my simple approach. This works without issues.
public partial class Form2 : Form
{
static int VALIDATION_DELAY = 1500;
System.Threading.Timer timer = null;
public Form2()
{
InitializeComponent();
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
TextBox origin = sender as TextBox;
if (!origin.ContainsFocus)
return;
DisposeTimer();
timer = new System.Threading.Timer(TimerElapsed, null, VALIDATION_DELAY, VALIDATION_DELAY);
}
private void TimerElapsed(Object obj)
{
CheckSyntaxAndReport();
DisposeTimer();
}
private void DisposeTimer()
{
if (timer != null)
{
timer.Dispose();
timer = null;
}
}
private void CheckSyntaxAndReport()
{
this.Invoke(new Action(() =>
{
string s = textBox1.Text.ToUpper(); //Do everything on the UI thread itself
label1.Text = s;
}
));
}
}
You can handle the LostFocus event of the text box which will fire everytime the user finishes typing and navigates away from the text box. Here is the documentation on LostFocus: http://msdn.microsoft.com/en-us/library/system.windows.forms.control.lostfocus.aspx
However, I am not sure what exactly you are trying to do here as the question is not very clear about what "finish" means.
In UWP, I did a delayed check by making a static lastTimeOfTyping and checking the time when the "TextChanged" event happened. This waits till the static lastTimeOfTyping matches when a new "TextChanged" time matches and then executes the desired function.
private const int millisecondsToWait = 500;
private static DateTime s_lastTimeOfTyping;
private void SearchField_OnTextChanged(object sender, TextChangedEventArgs e)
{
var latestTimeOfTyping = DateTime.Now;
var text = ((TextBox)sender).Text;
Task.Run(()=>DelayedCheck(latestTimeOfTyping, text));
s_lastTimeOfTyping = latestTimeOfTyping;
}
private async Task DelayedCheck(DateTime latestTimeOfTyping, string text)
{
await Task.Delay(millisecondsToWait);
if (latestTimeOfTyping.Equals(s_lastTimeOfTyping))
{
// Execute your function here after last text change
// Will need to bring back to the UI if doing UI changes
}
}
As an asynchronous extension method. Adapted from Grecon14's answer.
Note: This is lacking any consideration for cursor position changes, so if the user is moving around with the arrow keys but not actually changing the text it would return true. The question states "finished typing" and I'm not sure if moving the cursor around constitutes actually typing, maybe? As a user I would want it to incorporate this activity. Unfortunately it would need to be more complex than the following for proper interface functionality. See SurfingSanta's answer which has a keydown subscription if you need that.
public static class UIExtensionMethods
{
public static async Task<bool> GetIdle(this TextBox txb)
{
string txt = txb.Text;
await Task.Delay(500);
return txt == txb.Text;
}
}
Usage:
if (await myTextBox.GetIdle()){
// typing has stopped, do stuff
}
I don't know if the onChange() only exists in an older version of c#, but I can't find it!
The following works for detecting when a user either hits the Enter key, or tabs out of the TextBox, but only after changing some text:
//--- this block deals with user editing the textBoxInputFile --- //
private Boolean textChanged = false;
private void textBoxInputFile_TextChanged(object sender, EventArgs e) {
textChanged = true;
}
private void textBoxInputFile_Leave(object sender, EventArgs e) {
if (textChanged) {
fileNameChanged();
}
textChanged = false;
}
private void textBoxInputFile_KeyDown(object sender, KeyEventArgs e) {
if (textChanged & e.KeyCode == Keys.Enter) {
fileNameChanged();
}
textChanged = false;
}
//--- end block --- //
You can use textbox onChange() event. If text is changed in textbox, check if entered value is a number and calculate total value according to the other value.
You want to use handle either the Leave or LostFocus event for the textbox in question. I'm assuming you are using WinForm even though you don't state it in your question.
What if you trigger an event based on a keystroke like tab or return?
A coworker of mine suggested a solution using Rx and event throttling:
var FindDelay = 500;//milliseconds
//textBox is your text box element
Observable.FromEventPattern<EventArgs>(textBox, "TextChanged")
.Select(ea => ((TextBox) ea.Sender).Text)
.DistinctUntilChanged()
.Throttle(TimeSpan.FromMilliseconds(FindDelay))
.Subscribe(text => {
//your handler here
});
Ideally an inheritance solution like esskar’s is the way to go but it doesn’t play well with the designer so to get the re-use I opted for a helper style side-class:
using System;
using System.Threading;
using System.Windows.Forms;
using Timer = System.Threading.Timer;
internal class DelayedText : IDisposable
{
private readonly EventHandler _onTextChangedDelayed;
private readonly TextBox _textBox;
private readonly int _period;
private Timer _timer;
public DelayedText(TextBox textBox, EventHandler onTextChangedDelayed, int period = 250)
{
_textBox = textBox;
_onTextChangedDelayed = onTextChangedDelayed;
_textBox.TextChanged += TextBoxOnTextChanged;
_period = period;
}
public void Dispose()
{
_timer?.Dispose();
_timer = null;
}
private void TextBoxOnTextChanged(object sender, EventArgs e)
{
Dispose();
_timer = new Timer(TimerElapsed, null, _period, Timeout.Infinite);
}
private void TimerElapsed(object state)
{
_onTextChangedDelayed(_textBox, EventArgs.Empty);
}
}
Usage, in the form constructor:
InitializeComponent();
...
new DelayedText(txtEdit, txtEdit_OnTextChangedDelayed);
I haven't kicked it hard, but seems to work for me.
If user is typing fast and you want to delay the calculation until he stopped typing then below code may help:
private void valueInput_TextChanged(object sender, EventArgs e)
{
CalculateAfterStopTyping();
}
Thread delayedCalculationThread;
int delay = 0;
private void CalculateAfterStopTyping()
{
delay += 200;
if (delayedCalculationThread != null && delayedCalculationThread.IsAlive)
return;
delayedCalculationThread = new Thread(() =>
{
while (delay >= 200)
{
delay = delay - 200;
try
{
Thread.Sleep(200);
}
catch (Exception) {}
}
Invoke(new Action(() =>
{
// do your calcualation here...
}));
});
delayedCalculationThread.Start();
}
Most straight forward approach.
*.xaml
<TextBox Name="Textbox1"
TextChanged="Textbox1_TextChanged"/>
*.xaml.cs
using System.Threading.Tasks;
public bool isChanging = false;
async private void Textbox1_TextChanged(object sender,
TextChangedEventArgs e)
{
// entry flag
if (isChanging)
{
return;
}
isChanging = true;
await Task.Delay(500);
// do your stuff here or call a function
// exit flag
isChanging = false;
}
I had the same problem and i think the simplest solution is to use the LostFocus event:
xaml
<TextBox x:Name="YourTextBox" LostFocus="YourTextBox_LostFocus" />
xaml.cs
private void YourTextBox_LostFocus(object sender, RoutedEventArgs e)
{
//Your code here
}
I wanted to commit a textbox both on Return/Tab and on LostFocus, so i have used this convoluted solution, but it works.
public static void TextBoxEditCommit(TextBox tb, Action<TextBox>OnEditCommit)
{
if (OnEditCommit == null)
throw new ArgumentException("OnEditCommit delegate is mandatory.");
//THis delegate fire the OnEditCommit Action
EventHandler _OnEditCommit = delegate(object sender, EventArgs e)
{ OnEditCommit(tb); };
//Edit commit on Enter or Tab
tb.KeyDown += delegate (object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter || e.KeyCode == Keys.Tab)
{
//Temporary remove lostfocus event for avoid double commits
tb.LostFocus -= _OnEditCommit;
OnEditCommit(tb);
tb.LostFocus += _OnEditCommit;
}
};
//Edit commit on LostFocus
tb.LostFocus += _OnEditCommit;
}
You can use this event generator with this simple code:
//Check for valid content
UIUtil.TextBoxEditCommit(tbRuleName, (tb) => {
//Your code here, tb.text is the value collected
});

How to make Combobox in winforms readonly

I do not want the user to be able to change the value displayed in the combobox. I have been using Enabled = false but it grays out the text, so it is not very readable. I want it to behave like a textbox with ReadOnly = true, where the text is displayed normally, but the user can't edit it.
Is there is a way of accomplishing this?
make DropDownStyle property to DropDownList instead of DropDown
then handle the TextChanged event to prevent user changing text.
The article ComboBox-with-read-only-behavior suggests an interesting solution:
Create both a readonly textbox and a combobox in the same place. When you want readonly mode, display the textbox, when you want it to be editable, display the combobox.
Not sure if this is what you're looking for but...
Set the DropDownStyle = DropDownList
Then on the SelectedIndexChanged event
if (ComboBox1.SelectedIndex != 0)
{
ComboBox1.SelectedIndex = 0;
}
This ugly part is that they will "feel" like they can change it. They might think this is an error unless you give them an alert telling them why they can't change the value.
The best thing I can suggest is to replace the combo-box with a read-only textbox (or just perhaps a label) - that way the user can still select/copy the value, etc.
Of course, another cheeky tactic would be to set the DropDownStyle to DropDownList, and just remove all other options - then the user has nothing else to pick ;-p
enter link description here
Just change the DropDownStyle to DropDownList. Or if you want it completely read only you can set Enabled = false, or if you don't like the look of that I sometimes have two controls, one readonly textbox and one combobox and then hide the combo and show the textbox if it should be completely readonly and vice versa.
Here is the Best solution for the ReadOnly Combo.
private void combo1_KeyPress(object sender, KeyPressEventArgs e)
{
e.KeyChar = (char)Keys.None;
}
It will discard the keypress for the Combo.
I've handled it by subclassing the ComboBox to add a ReadOnly property that hides itself when set and displays a ReadOnly TextBox on top containing the same Text:
class ComboBoxReadOnly : ComboBox
{
public ComboBoxReadOnly()
{
textBox = new TextBox();
textBox.ReadOnly = true;
textBox.Visible = false;
}
private TextBox textBox;
private bool readOnly = false;
public bool ReadOnly
{
get { return readOnly; }
set
{
readOnly = value;
if (readOnly)
{
this.Visible = false;
textBox.Text = this.Text;
textBox.Location = this.Location;
textBox.Size = this.Size;
textBox.Visible = true;
if (textBox.Parent == null)
this.Parent.Controls.Add(textBox);
}
else
{
this.Visible = true;
this.textBox.Visible = false;
}
}
}
}
Michael R's code works, but...
The DropDownHeight = 1; must be back to the default value when ReadOnly property is set to false. So, insert before base.OnDropDown(e): DropDownHeight = 106;
using System;
using System.Threading;
using System.Windows.Forms;
namespace Test_Application
{
class ReadOnlyComboBox : ComboBox
{
private bool _readOnly;
private bool isLoading;
private bool indexChangedFlag;
private int lastIndex = -1;
private string lastText = "";
public ReadOnlyComboBox()
{
}
public bool ReadOnly
{
get { return _readOnly; }
set { _readOnly = value; }
}
protected override void OnDropDown (EventArgs e)
{
if (_readOnly)
{
DropDownHeight = 1;
var t = new Thread(CloseDropDown);
t.Start();
return;
}
DropDownHeight = 106; //Insert this line.
base.OnDropDown(e);
}
private delegate void CloseDropDownDelegate();
private void WaitForDropDown()
{
if (InvokeRequired)
{
var d = new CloseDropDownDelegate (WaitForDropDown);
Invoke(d);
}
else
{
DroppedDown = false;
}
}
private void CloseDropDown()
{
WaitForDropDown();
}
protected override void OnMouseWheel (MouseEventArgs e)
{
if (!_readOnly)
base.OnMouseWheel(e);
}
protected override void OnKeyDown (KeyEventArgs e)
{
if (_readOnly)
{
switch (e.KeyCode)
{
case Keys.Back:
case Keys.Delete:
case Keys.Up:
case Keys.Down:
e.SuppressKeyPress = true;
return;
}
}
base.OnKeyDown(e);
}
protected override void OnKeyPress (KeyPressEventArgs e)
{
if (_readOnly)
{
e.Handled = true;
return;
}
base.OnKeyPress(e);
}
}
}
To complete this answer:
File -> New -> Project... Visual C# -> Windows -> Classic Desktop ->
Windows Forms Control Library
type the Name of your control - OK and paste this code.
You can choose the name of your dll file:
Project - yourproject Properties...
Assembly name: type the name. Just build the solution and you have your dll file. So, open the project where you want to use your Read Only combo, right click on References
Add Reference... and browse your dll file. To Insert your custom component into Toolbox, open your Toolbox, right click on General tab -> Choose Items...
Browse your dll file - Open. Now you can use your ReadOnlyComboBox in your projects. PS: I'm using VS2015.
You can change the forecolor and backcolor to the system colors for an enabled combo box, although this may confuse the users (why have it if they can't change it), it will look better.
This is how you would address the fact that a ComboBox with Enabled = False is hard to read:
A combobox that looks decent when it is disabled
Actually, its rather simple:
Private Sub combobox1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles combobox1.KeyDown
' the following makes this the combobox read only
e.SuppressKeyPress = True
End Sub
Why don't you just use a text box? Text box has a "Read only" property, and since you want your combo box only to display data, I don't see why you would need a combo box.
An alternative is that you just cancel out the input for the "on value changed" event. That way you will be displaying your information no mater what user does ...
Set DropdownStyle Property to Simple
Add below code to to KeyPress event of ComboBox
private void comboBoxName_KeyPress(object sender, KeyPressEventArgs e)
{
e.Handled = true;
return;
}
Add below code to to KeyDown event of ComboBox
private void comboBoxName_KeyDown(object sender, KeyEventArgs e)
{
e.Handled = true;
return;
}
If you've already populated it, and selected the appropriate item, and made it a DropDownList, then you can use an extension method like this to quickly reduce the selection list down to just the selected item:
public static void MakeReadOnly(this ComboBox pComboBox) {
if (pComboBox.SelectedItem == null)
return;
pComboBox.DataSource = new List<object> {
pComboBox.SelectedItem
};
}
I know that I'm a little late to the party, but I was researching this exact question and I knew that there had to be some way to make the combobox readonly as if it were a textbox and disabled the list popping up. It's not perfect, but it is definitely better than all of the answers I've been finding all over the internet that don't work for me. After the button is pressed and the OnDropDown is called, a new thread is created that will set the DroppedDown property to false, thus creating the effect of "nothing happening." The mouse wheel is consumed and key events are consumed as well.
using System;
using System.Threading;
using System.Windows.Forms;
namespace Test_Application
{
class ReadOnlyComboBox : ComboBox
{
private bool _readOnly;
private bool isLoading;
private bool indexChangedFlag;
private int lastIndex = -1;
private string lastText = "";
public ReadOnlyComboBox()
{
}
public bool ReadOnly
{
get { return _readOnly; }
set { _readOnly = value; }
}
protected override void OnDropDown(EventArgs e)
{
if (_readOnly)
{
DropDownHeight = 1;
var t = new Thread(CloseDropDown);
t.Start();
return;
}
base.OnDropDown(e);
}
private delegate void CloseDropDownDelegate();
private void WaitForDropDown()
{
if (InvokeRequired)
{
var d = new CloseDropDownDelegate(WaitForDropDown);
Invoke(d);
}
else
{
DroppedDown = false;
}
}
private void CloseDropDown()
{
WaitForDropDown();
}
protected override void OnMouseWheel(MouseEventArgs e)
{
if (!_readOnly)
base.OnMouseWheel(e);
}
protected override void OnKeyDown(KeyEventArgs e)
{
if (_readOnly)
{
switch (e.KeyCode)
{
case Keys.Back:
case Keys.Delete:
case Keys.Up:
case Keys.Down:
e.SuppressKeyPress = true;
return;
}
}
base.OnKeyDown(e);
}
protected override void OnKeyPress(KeyPressEventArgs e)
{
if (_readOnly)
{
e.Handled = true;
return;
}
base.OnKeyPress(e);
}
}
}
Simplest way in code:
instead of adding methods for KeyPress or KeyDown,
add this code on 'Form1_Load' method:
comboBox1.KeyPress += (sndr, eva) => eva.Handled = true;
or
comboBox1.KeyDown += (sndr, eva) => eva.SuppressKeyPress = true;
(sndr, eva) is for (object sender, EventArgs e)
I dont know if that is what you are looking but this prevents the user from chosing any item from the drop down and still be able to type text in the combobox. If you dont want the user to type text in the combobox you can make it Dropdown list from the properties menu.
So you get Read Only combobox.
On Selected Index Changed
Make the selected Index -1 "comboBox.SelectedIndex = -1";
private void MyComboBox_comboBox_SelectedIndexChanged(object sender, EventArgs e)
{
MyComboBox_comboBox.SelectedIndex = -1;
}
Here is the Best solution for the ReadOnly Combo.
private void combo1_KeyPress(object sender, KeyPressEventArgs e) {
e.KeyChar = (char)Keys.None;
}
It will discard the keypress for the Combo. It doesn't have "e.KeyChar" !

Categories