I have a wizard to make a project, I make us e of a tabcontrol. I have buttons to go to the next tab or the previous. My problem now is that even thought there is validation on the buttons for required field, you can still switch between the tabs by clicking the tab headers. If I disable the tabcontrol, the user can't use whatever is inside the tabs either. Can I solve this?
UPDATE: all the code of the wizard form
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WorldEstablisher
{
public partial class ProjectWizard : Form
{
#region variables
public Form1 MainForm { get; set; }
#endregion
#region constructor and page load
public ProjectWizard(Form1 form)
{
InitializeComponent();
MainForm = form;
}
private void ProjectWizard_Load(object sender, EventArgs e)
{
}
#endregion
#region navigation
private void nextButton_Click(object sender, EventArgs e)
{
if (tabs.SelectedIndex == 0)//field validation tab 1
{
if (folderLocationTextBox.Text != "" && worldNameTextBox.Text != "")
{
backButton.Visible = true;
tabs.SelectedIndex = tabs.SelectedIndex + 1;
}
}
if (tabs.SelectedIndex == 1)//field validation tab 2
{
if (authorTextBox.Text != "")
{
tabs.SelectedIndex = tabs.SelectedIndex + 1;
}
}
if (tabs.SelectedIndex == 2)
{
finishButton.Visible = true;
}
}
private void backButton_Click(object sender, EventArgs e)
{
if (tabs.SelectedIndex != 0)
{
tabs.SelectedIndex = tabs.SelectedIndex - 1;
if (tabs.SelectedIndex == 0)//Make the back button invisible
{
backButton.Visible = false;
}
if (tabs.SelectedIndex != 2)//Make the finish button invisible
{
finishButton.Visible = false;
}
}
}
private void finishButton_Click(object sender, EventArgs e)
{
World world = new World("test");
MainForm.CurrentWorld = world;
this.Close();
}
#endregion
private void selectFolderButton_Click(object sender, EventArgs e)
{
if (folderBrowser.ShowDialog() == DialogResult.OK)
{
folderLocationTextBox.Text = folderBrowser.SelectedPath;
}
}
}
}
There should be a better way to do this, but in the SelectedIndexChanged event for the TabControl you can set the SelectedTab to be whichever TabPage you want it to be. Track the current TabPage when the user clicks on your navigation buttons.
private TabPage currentTabPage;
private void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
{
tabControl1.SelectedTab = currentTabPage;
}
Since you'd be basically eliminating the built-in navigation of a TabControl, I'd rethink your design. Use a stack of Panel objects that you show or hide based on your button navigation mechanism. Don't confuse the user by showing them tab buttons that they can't ever use.
Related
I'm using Microsoft Visual Studio 2013 and writing in c#. I have written a descendant of the TabControl class and overridden the OnClick method, then changed my existing TabControl element to use the new class. Everything compiles and runs, my breakpoint in the constructor is reached, but it's not using the OnClick override! Here's the TabControl descendant code, thanks in advance for any help!
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace LumaSense.Imaging.Calibration.UI
{
public partial class TabControlModified : TabControl
{
public TabControlModified()
{
InitializeComponent();
}
private bool superuser;
public int lastTabSelectedIndex = 0;
public bool Superuser
{
get { return superuser; }
set { superuser = value; }
}
public int LastTabSelectedIndex
{
get { return lastTabSelectedIndex; }
set
{ this.lastTabSelectedIndex = value; }
}
protected override void OnClick(EventArgs e)
{
// SelectedIndex and tab have already changed before we get here
if (this.superuser == false)
{
if (this.SelectedIndex <= this.lastTabSelectedIndex)
{
this.LastTabSelectedIndex = this.SelectedIndex;
base.OnClick(e);
}
else
{
base.OnClick(e);
this.SelectedIndex = lastTabSelectedIndex;
}
}
else
{
this.LastTabSelectedIndex = this.SelectedIndex;
base.OnClick(e);
}
}
}
}
This snippet works just fine. You need to make sure you have added pages to your tab control in order for the tabs to display and accessible to the user to click.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
var mTab = new MyTab();
mTab.Location = new System.Drawing.Point(100, 100);
// The OnClick will only work on the
// Tabs themselves so Pages must be added to display the Tabs.
var mtabPage1 = new System.Windows.Forms.TabPage();
mTab.Controls.Add(mtabPage1);
this.Controls.Add(mTab);
}
class MyTab : TabControl
{
protected override void OnClick(EventArgs e)
{
MessageBox.Show("I was clicked.");
base.OnClick(e);
}
}
}
I found out that using Deselecting and Selecting instead of creating a descendent of TabControl fixed the problem. Somehow, when using the TabControl descendant, adding the Reactive UI binding of TabControlModified .SelectedIndex messed things up and the overridden OnClick was no longer callerd.
I am displaying all of the worlds countries in a combobox called countrybox.
A second combobox contains all of the cities in a country called citybox.
When you choose a country, citybox appears and when you open countrybox, citybox dissapears again.
The problem arises when you open citybox and click outside of it.
citybox dissapears when you
open countrybox and does not come back when you click outside of countrybox.
I tried with this:
string ctext { get; set; }
private void countrybox_SelectedIndexChanged(object sender, EventArgs e)
{
citybox.Visible = true;
string ctext = countrybox.Text;
}
private void countrybox_DropDownClosed(object sender, EventArgs e)
{
if (countrybox.Text == ctext)
{
citybox.Visible = true;
}
else
{
citybox.Visible = false;
}
However, this did not work as i wanted it to.
Im guessing it is because the Combobox class does not recognize a click outside of the box as a _DropDownClosed event.
I have also tried with using the
validating event to check if a user clicks on the form
private void countrybox_Validating(object sender, CancelEventArgs e)
{
if (string.Equals((sender as Form).Name, #"Form1") && string.IsNullOrEmpty(countrybox.Text))
{
e.Cancel = true;
MessageBox.Show("You have to select a country!");
}
}
Is there anyway to make the combobox dropdownlist to not close when clicked outside of the list?
I apologize if i have any Spelling misstakes, my native language is not English.
Posting my full code below for people who want more details.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
string[] Countries = File.ReadAllLines(#"C:\Travelbureau\Countries.txt");
Array.Sort(Countries);
for (int i = 0; i < Countries.Length; i++)
{
countrybox.Items.Add(Countries[i]);
}
}
string ctext { get; set; }
private void countrybox_SelectedIndexChanged(object sender, EventArgs e)
{
citybox.Visible = true;
string ctext = countrybox.Text;
switch (countrybox.Text)
{
case "Afghanistan":
string[] AfgCity = File.ReadAllLines(#"C:\Travelbureau\Afghanistan.txt");
Array.Sort(AfgCity);
for (int i = 0; i < AfgCity.Length; i++)
{
citybox.Items.Add(AfgCity[i]);
}
break;
default:
citybox.Text = "City";
citybox.Items.Clear();
break;
}
}
private void countrybox_DropDown(object sender, EventArgs e)
{
citybox.Visible = false;
}
private void countrybox_DropDownClosed(object sender, EventArgs e)
{
if (countrybox.Text == ctext)
{
citybox.Visible = true;
}
else
{
citybox.Visible = false;
}
private void countrybox_Click(object sender, EventArgs e)
{
countrybox.Text = "";
citybox.Visible = false;
}
private void citybox_Click(object sender, EventArgs e)
{
citybox.Text = "";
}
private void countrybox_Validating(object sender, CancelEventArgs e)
{
if (string.Equals((sender as Form).Name, #"Form1") && string.IsNullOrEmpty(countrybox.Text))
{
e.Cancel = true;
MessageBox.Show("You have to select a country!");
}
}
}
}
It's been a while since I did anything with Windows Forms, but you should be able to accomplish this with the Validating event. If you check in that to see whether it has a value and set e.Cancel accordingly, I believe that will give you your desired behavior.
In my experience, it's a very good idea to set some sort of status visible to the user in that event, though. It can be confusing when a user is no longer able to click anywhere. You can do that with a MessageBox or a Label. But that's really a UX matter that isn't directly relevant to your question.
You might also have to set a property that indicates that it should validate when you leave the control, but I don't believe that's the case. If someone knows for sure I'll update this answer to reflect that.
And yes, you can change the visibility of cityBox in that method as well.
I have a form with 2 tabs on it. I can chose the tab viewed after initialization and I need some initial code every time after the tab2 is initialized:
public partial class SetupComponent : Form
{
public SetupComponent(bool tab2)
{
InitializeComponent();
if (tab2)
{
this.tabControl1.SelectedTab = tabPage2;
}
}
private void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
{
textBox1.SelectionStart = textBox1.Text.Length;
textBox1.ScrollToCaret();
textBox2.SelectionStart = textBox2.Text.Length;
textBox2.Focus();
}
}
if I call this class with tab2=false and then click onto tab2, tabControl1_SelectedIndexChanged is called.
But if I select the tab2=true during SetupComponent, I find no possibility to do that code. All the TabControl1_Events I found are too early and I don`t find a matching TabPage2_Event.
How can I manage it?
I managed this problem using the Paint_Event:
bool activated = false;
private void tabPage2_Paint(object sender, PaintEventArgs e)
{
if (!activated)
{
tabControl1_SelectedIndexChanged(null, null);
activated = true;
}
}
I use the variable because the Paint_Event is called many times.
I am using a ToolStrip with a number of ToolStripButtons.
What I would like is to be able to flash one of the buttons to get the user's attention.
For example, if they have made changes to information and need to click the Save button.
If this were a normal button I could do this using a Timer and periodically changing the BackColor however this doesn't work with a ToolStrip.
I could create a Renderer subclass and assign it to the ToolStrip but this appears to only get used in specific situations - i.e. it's event driven.
Does anyone have any ideas?
Well, just use a custom renderer so you can change the color of the button's background. With a timer that blinks it. Add a new class to your project and paste this code:
using System;
using System.Drawing;
using System.Collections.Generic;
using System.Windows.Forms;
class BlinkingButtonRenderer : ToolStripProfessionalRenderer {
public BlinkingButtonRenderer(ToolStrip strip) {
this.strip = strip;
this.strip.Renderer = this;
this.strip.Disposed += new EventHandler(strip_Disposed);
this.blinkTimer = new Timer { Interval = 500 };
this.blinkTimer.Tick += delegate { blink = !blink; strip.Invalidate(); };
}
public void BlinkButton(ToolStripButton button, bool enable) {
if (!enable) blinkButtons.Remove(button);
else blinkButtons.Add(button);
blinkTimer.Enabled = blinkButtons.Count > 0;
strip.Invalidate();
}
protected override void OnRenderButtonBackground(ToolStripItemRenderEventArgs e) {
var btn = e.Item as ToolStripButton;
if (blink && btn != null && blinkButtons.Contains(btn)) {
Rectangle bounds = new Rectangle(Point.Empty, e.Item.Size);
e.Graphics.FillRectangle(Brushes.Black, bounds);
}
else base.OnRenderButtonBackground(e);
}
private void strip_Disposed(object sender, EventArgs e) {
blinkTimer.Dispose();
}
private List<ToolStripItem> blinkButtons = new List<ToolStripItem>();
private bool blink;
private Timer blinkTimer;
private ToolStrip strip;
}
Sample usage in a form with a Toolstrip containing a button:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
blinker = new BlinkingButtonRenderer(toolStrip1);
}
private void toolStripButton1_Click(object sender, EventArgs e) {
blink = !blink;
blinker.BlinkButton(toolStripButton1, blink);
}
private bool blink;
private BlinkingButtonRenderer blinker;
}
I am trying to write a text editor using RichTextBox. My concern is now about Undo and possibly Redo features of RichTextBox.
When I start writing in text box, say 1 minute! if I call Undo method, all it does is just I beleive clearing or resetting richtextbox again. How can I get it to work that it can do better, like Undoing last added word, or last added new line...I mean usual things you expect from Undo function. (The same counts for Redo also!)
Is there properties or some options to achive this? Or I have to implement my own code?
Just to go on from ahmadali's code - you can put it into a seperate class, and implement the redo functionality also:
NB. yes, it saves all the text every time the textbox is changed, so you can change that if your app will be used for massive amounts of text or if the app will be open for extended periods (ie days/weeks)
public partial class MainForm : Form
{
Undoer undoer;
public MainForm()
{
InitializeComponent();
this.txtBox.TextChanged += new EventHandler( TextBoxTextChanged );
this.undoer = new Undoer(ref this.txtText);
// create a context menu
ContextMenu menu = new ContextMenu();
menu.MenuItems.AddRange( new MenuItem[] {
new MenuItem("&Undo", new EventHandler( this.undoer.undo_Click )),
new MenuItem("&Redo", new EventHandler( this.undoer.redo_Click ))
});
this.txtBox.ContextMenu = menu;
// or create keypress event
this.txtBox.KeyDown += new KeyEventHandler( textBox_KeyDown );
this.KeyDown += new KeyEventHandler( textBox_KeyDown );
}
protected void TextBoxTextChanged(object sender, EventArgs e)
{
undoer.Save();
}
protected void textBox_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
if (e.Modifiers == (System.Windows.Forms.Keys.Control))
{
if ( e.KeyCode == Keys.Z )
{
this.undoer.Undo();
e.Handled = true;
}
if ( e.KeyCode == Keys.Y )
{
this.undoer.Redo();
e.Handled = true;
}
}
}
}
public class Undoer
{
protected System.Windows.Forms.RichTextBox txtBox;
protected List<string> LastData = new List<string>();
protected int undoCount = 0;
protected bool undoing = false;
protected bool redoing = false;
public Undoer(ref System.Windows.Forms.RichTextBox txtBox)
{
this.txtBox = txtBox;
LastData.Add(txtBox.Text);
}
public void undo_Click(object sender, EventArgs e)
{
this.Undo();
}
public void redo_Click(object sender, EventArgs e)
{
this.Redo();
}
public void Undo()
{
try
{
undoing = true;
++undoCount;
txtBox.Text = LastData[LastData.Count - undoCount - 1];
}
catch { }
finally{ this.undoing = false; }
}
public void Redo()
{
try
{
if (undoCount == 0)
return;
redoing = true;
--undoCount;
txtBox.Text = LastData[LastData.Count - undoCount - 1];
}
catch { }
finally{ this.redoing = false; }
}
public void Save()
{
if (undoing || redoing)
return;
if (LastData[LastData.Count - 1] == txtBox.Text)
return;
LastData.Add(txtBox.Text);
undoCount = 0;
}
}
You can save the lastest Data and when you want to undo you can change to now data to last data! lastest data can be set anytime that you want!
I Make a winForm with a richTextBox and a button that button undo the wrote text:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace test
{
public partial class Form1 : Form
{
List<string> LastData = new List<string>();
int undoCount = 0;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
LastData.Add(richTextBox1.Text);
}
private void button1_Click(object sender, EventArgs e)
{
try
{
richTextBox1.Text = LastData[LastData.Count - undoCount - 1];
++undoCount;
}
catch { }
}
private void richTextBox1_KeyPress(object sender, KeyPressEventArgs e)
{
LastData.Add(richTextBox1.Text);
undoCount = 0;
}
}
}
but I didn't find any better and organized way and you can change
LastData.Add(richTextBox1.Text);
undoCount = 0;
to save new words or new line
update:
if you want to save the Ram you can delete the first data on list after many undo saving.