How to implement the Edit -> Copy menu in c#/.net - c#

How do I implement a Copy menu item in a Windows application written in C#/.NET 2.0?
I want to let the user to mark some text in a control and then select the Copy menu item from an Edit menu in the menubar of the application and then do a Paste in for example Excel.
What makes my head spin is how to first determine which child form is active and then how to find the control that contains the marked text that should be copied to the clipboard.
Help, please.

With the aid of some heavy pair programming a colleague of mine and I came up with this, feel free to refactor.
The code is placed in the main form. The copyToolStripMenuItem_Click method handles the Click event on the Copy menu item in the Edit menu.
/// <summary>
/// Recursively traverse a tree of controls to find the control that has focus, if any
/// </summary>
/// <param name="c">The control to search, might be a control container</param>
/// <returns>The control that either has focus or contains the control that has focus</returns>
private Control FindFocus(Control c)
{
foreach (Control k in c.Controls)
{
if (k.Focused)
{
return k;
}
else if (k.ContainsFocus)
{
return FindFocus(k);
}
}
return null;
}
private void copyToolStripMenuItem_Click(object sender, EventArgs e)
{
Form f = this.ActiveMdiChild;
// Find the control that has focus
Control focusedControl = FindFocus(f.ActiveControl);
// See if focusedControl is of a type that can select text/data
if (focusedControl is TextBox)
{
TextBox tb = focusedControl as TextBox;
Clipboard.SetDataObject(tb.SelectedText);
}
else if (focusedControl is DataGridView)
{
DataGridView dgv = focusedControl as DataGridView;
Clipboard.SetDataObject(dgv.GetClipboardContent());
}
else if (...more?...)
{
}
}

Why not extending the control, so the control itself provides the data which should be copied into the clipboard.
Take a look at ApplicationCommands documentation.

To determine which window is open, you can query the Form.ActiveMDIChild property to get a reference to the currently active window. From there, you can do one of two things:
1) If you create your own custom Form class (FormFoo for example) that has a new public member function GetCopiedData(), then inherit all of your application's child forms from that class, you can just do something like this:
((FormFoo)this.ActiveMDIChild).GetCopiedData();
Assuming the GetCopiedData function will have the form-specific implementation to detect what text should be copied to the clipboard.
or
2) You can use inheritance to detect the type of form that is active, and then do something to get the copied data depending on the type of form:
Form f = this.ActiveMDIChild;
if(f is FormGrid)
{
((FormGrid)f).GetGridCopiedData();
} else if(f is FormText) {
((FormText)f).GetTextCopiedData();
}
etc.
That should get you started with finding the active window and how to implement a copy function. If you need more help copying out of a GridView, it may be best to post another question.

If the form is tabbed and the target control is a DataGridView, it's sometimes possible for the Form's TabControl to be returned as the active control, using the above method, when the DataGridView is right clicked upon.
I got around this by implementing the following handler for my DataGridView:-
private void dataGridView_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
dataGridView.Focus();
dataGridView.CurrentCell = dataGridView[e.ColumnIndex, e.RowIndex];
}
}

It seems to me that you might be better off breaking this into smaller tasks/questions.
You have a few issues you are stuck on from the way it sounds.
You have multiple 'child' windows open. Is this an MDI application?
When an action is performed on one of those child windows, it should fire an event in that window's event handlers. That is your first thing to set up. If this is a datagridview I would suggest a simple test to start. Try trapping the DataGridView.SelectionChanged event. Just throw in something like MessageBox.Show("I copied your datas!"); for now.
This should get you started where you will at least understand how this event will be raised to you.
From here, we will need to know a little more about your datagrid, and the rows and child controls in those rows. Then we can likely create events in the render events that will be raised at the appropriate times, with the appropriate scope.

Related

Orc.Memento Global Undo with Multiple Controls

I have a need to implement a memento undo-redo pattern. My application has multiple tabs and in these tabs there are multiple controls which all implement Orc.Memento. The trouble I am having is calling undo with the menu button on MainWindow and in the button action call undo on the last active control.
Edit: this project is unfortunately does not follow MVVM.
I chose Orc.Memento because it is super easy to implement without modifying objects. What I have now is working great only with keyboard commands Ctrl+X & Ctrl+Y. Calling undo only does undo on the active control. However, when I click the undo/redo buttons on the MainWindow menu my code does not know the last active control to call undo/redo on.
Option 1
Option one is to keep track of the last active control by setting a global property on GotFocus() of each control. I feel like there has to be a better way.
Option 2
That is why I am here :-).
Control
public class MyControl : IMemento
{
private MementoService mementoService = new MementoService();
public void RegisterAll()
{
mementoService.RegisterObject(myObject);
mementoService.RegisterCollection(myCollection);
}
public void Undo()
{
mementoService.Undo();
}
public void Redo()
{
mementoService.Redo();
}
}
MainWindow
Ctrl+Z & Ctrl+Y is mapped here. The undo/redo methods find the currently active control and call undo/redo at that control.
public MainWindow
{
/// <summary>
/// Call undo on the currently active control
/// </summary>
public void Undo()
{
/*
* get current focused control.
* find the parent that is an IMemento. And call Redo on that control
*/
var focusedControl = FocusManager.GetFocusedElement(this);
var mementoControl = UIHelper.TryFindParentThatIsIMemento<Control>(focusedControl as DependencyObject);
/*
* Call Undo on the control that is currently active
*/
if (mementoControl != null && mementoControl is IMemento)
{
var mem = (mementoControl as IMemento);
mem.Undo();
}
}
}
Note: If I could program this how Excel works by auto navigating to the control where the undo/redo happens that would be great. It is not necessary, but if you have an idea my ears are open.
Here are a few recommendations:
Try to implement undo/redo against models (e.g. using Orc.ProjectManagement), not against views (since views are short-living
Try to use the TabControl from Orc.Controls, which allows you to keep all the tabs active and thus allowable for redo/undo).

WinForms tooltips not showing up

I have a WinForms application. Each form and user control sets up its tooltips as follows:
// in the control constructor
var toolTip = new ToolTip();
this.Disposed += (o, e) => toolTip.Dispose();
toolTip.SetToolTip(this.someButton, "...");
toolTip.SetToolTip(this.someCheckBox, "...");
...
However, the tooltips don't appear when I hover over the controls. Is this an appropriate way to use tooltips? Is there something that could be happening in another part of the application (e. g. listening to some event) that would stop tooltips from working?
Note that tooltips on my outer form's toolstrip buttons (which are configured via the button's tooltip property) do work as expected.
EDIT:
I've observed this more and I've noticed that sometimes the tooltip does show up, it is just extremely "flaky". Basically, sometimes when I mouse over a control it will show up very briefly and then flicker away. I can get it to show manually with .Show() and a long AutoPopDelay, but then it never disappears!
Your code seems ok to me. I couldnt find anything wrong in your code. But, it could be failed only when control is disabled. BTW, you can try another method like this. but, i would not like to suggest you to show the tooltip like this.
private void someButton_MouseEnter(...)
{
toolTip.Show("Tooltip text goes here", (Button)sender);
}
You can also assign the location where tooltip should be displayed in .Show() method. there are some overloaded function that you can use. Read the msdn for more information about ToolTip.Show() method.
I faced similar issue when my tooltip was not showing up over the RichTextBox once in about 3-5 times it normally should. Even forcing it to show explicitly with toolTip.Show didn't help. Until I changed to the way mentioned by Shell - you have to tell where you want your tooltip to appear:
'Dim pnt As Point
pnt = control.PointToClient(Cursor.Position)
pnt.X += 10 ' Give a little offset
pnt.Y += 10 ' so tooltip will look neat
toolTip.Show(text, control, pnt)
This way my tooltip always appears when and where expected.
Good luck!
I wrote the following method to "propagate" ToolTips from parent controls (that have a tool tip set) to its child controls (unless they have their own overriding ToolTip).
It's designed to be dropped into the form or control you're starting with, but it could also just be turned into a static method where the "parent" argument is required.
/// <summary>Setting a toolTip on a custom control unfortunately doesn't set it on child
/// controls within its drawing area. This is a workaround for that.</summary>
private void PropagateToolTips(Control parent = null)
{
parent = parent ?? this;
string toolTip = toolTip1.GetToolTip(parent);
if (toolTip == "")
toolTip = null;
foreach (Control child in parent.Controls)
{
// If the parent has a toolTip, and the child control does not
// have its own toolTip set - set it to match the parent toolTip.
if (toolTip != null && String.IsNullOrEmpty(toolTip1.GetToolTip(child)))
toolTip1.SetToolTip(child, toolTip);
// Recurse on the child control
PropagateToolTips(child);
}
}
Note that the behaviour is undefined if you're using more than one ToolTip instance to manage parent and child control toolTips.

user control that other developers can add controls to it and "inherit" the behavior of all controls on my control

hi all and sorry for the confusing title... can't find the right words yet.
just out of curiosity, I am playing with c# user control, I created a control that is built from a panel, a textbox that is being used as a filter, and some labels / buttons/ etc... that are being filtered.
when ever you change the text of the textbox, all the controls on the panel are visible / invisible depending if their Text property contains the Text of the textbox. very simple.
but I want this user control to be such that the user that uses it can drop more labels or controls to it and they will behave the same, I can't figure out how to do that..
when I am editing the control (adding controls to it), it works as expected and the new controls behave as the old ones without code modifications, but only when I am editing the user control and not when using it.
when I am dragging the user control to a form, I can not add controls to it... when I try to add a label to the control - it is just added to the form and not to the control and therefore the text box is not influencing the added label. what should I do if I want to be able to add the control to a form and then add some controls to the control?
I will be happy for some pointers.
here is the relevant code:
private void textBox1_TextChanged(object sender, EventArgs e)
{
foreach (Control c in panel1.Controls)
{
if (c.Text.Contains(textBox1.Text))
{
c.Visible = true;
}
else
{
c.Visible = false;
}
}
}
edit - pictures added.
as you can see - i typed 1 in the filter text box and all the controls except button1 are now invisible - and of course the bad behaving label.
Thanks,
Jim.
This problem can be solved easily by following the guidelines in
https://support.microsoft.com/en-us/kb/813450 which decribes step by step How to make a UserControl object acts as a control container design-time by using Visual C#
In order to modify the user control as a design time control container
add the following code to the Declarations section:
using System.ComponentModel.Design;
Apply the System.ComponentModel.DesignerAttribute attribute to the control as follows:
[Designer("System.Windows.Forms.Design.ParentControlDesigner, System.Design", typeof(IDesigner))]
public class UserControl1 : System.Windows.Forms.UserControl
{
...
}
Then build the solution.
the control will appear as usual in the Toolbox and can be added to forms. Additional controls such as buttone , text boxes etc.. can be added to the control as required.
You describe one of the reasons why I almost never use UserControls. Anything that isn't done to the original UC must be done in code..
You can instead make it a class that is not a UserControl, ie make it a simple subclass of Panel (or FlowLayoutPanel as I do here merely for convenience, while dropping stuff on it during my tests).
class FilterPanel : FlowLayoutPanel
{
TextBox tb_filterBox { get; set; }
Label st_filterLabel { get; set; }
public FilterPanel()
{
st_filterLabel = new Label();
st_filterLabel.Text = "Filter:";
this.Controls.Add(st_filterLabel);
tb_filterBox = new TextBox();
this.Controls.Add(tb_filterBox);
// st_filterLabel.Location = new Point(10, 10); // not needed for a FLP
// tb_filterBox.Location = new Point(100, 10); // use it for a Panel!
tb_filterBox.TextChanged += tb_filterBox_TextChanged;
}
void tb_filterBox_TextChanged(object sender, EventArgs e)
{
foreach(Control ctl in this.Controls)
{
if (ctl != tb_filterBox && ctl != st_filterLabel)
ctl.Visible = ctl.Text.Contains(tb_filterBox.Text);
}
}
}
Now after placing it on a form (or whatever) you (or whoever) can drop Controls onto it in the designer and they'll be part of its Controls collection, just like you want it and will behave as expected..
Two notes on subclassing Controls:
If you break one during developement, the Form(s) using it will be broken, too, until you fix the problem. So take a little extra care!
For the Designer to display the Control it always needs to have one parameterless constructor, like the one above. Even if you prefer to have the ability to hand in parameters, one parameterless constructor must still be there or the designer will get into trouble!

customizing treeview in winforms

Is it possible to create a treeview in visual studio which resembles the following figure :
The ROOT , CHILD and Sub-Child , all three would be LinkLabels , and on clicking them a new Form would be opened.
You could also try embed WPF user control into WinForm. Customizing WinForms isn't an easy task. In WPF you can do it much easier.
You also can activate Hot tracking for the tree view and then handle the NodeMouseClick event.
This is not practical in Winforms, every Control has a native Windows window associated with it. A window is a very expensive operating system object, create more than 50 of them and your user interface will noticeably start to drag because of the amount of overhead involved in drawing the controls. You very quickly reach that practical upper limit by nesting controls like you are intending to do.
You can customize the appearance of a TreeView by using its DrawMode property and the DrawNode event. The MSDN library article for TreeView.DrawNode has a decent example. It is also a popular component type in 3rd party component vendor collections. They add lots of bells and whistles to their version.
So, people don't like doing it.
The answer, however, is Yes, you can.
TreeView treeView1;
void Initialize_It() {
treeView1 = new TreeView();
treeView1.AfterSelect += new TreeViewEventHandler(treeView1_AfterSelect);
TreeNode Root = treeView1.Nodes.Add("ROOT");
TreeNode Child = Root.Nodes.Add("CHILD");
TreeNode SubChild = Child.Nodes.Add("Sub-Child");
}
void treeView1_AfterSelect(object sender, TreeViewEventArgs e) {
const string FORMAT = "{0} Node Selected. Call your Windows Form from here.";
if (e.Node.Level == 0) {
MessageBox.Show(string.Format(FORMAT, e.Node.Text), e.Node.Text);
} else if (e.Node.Level == 1) {
MessageBox.Show(string.Format(FORMAT, e.Node.Text), e.Node.Text);
} else if (e.Node.Level == 2) {
MessageBox.Show(string.Format(FORMAT, e.Node.Text), e.Node.Text);
}
}

Dynamically Created User Controls In C#

I am working in a C# winforms project and I have a user control which gets loaded upon its selection from a tool-strip menu. I have a dictionary lookup set to occur upon form load of the user control for other functionality. Also, when I close the user control I am just using the ".Hide();" method. I noticed that when I load the user control the first time everything is fine, but when I close it and choose to open it again the 2nd time it creates a new instance of the object thus throwing off my dictionary lookup. Therefore, I wrote some code in an attempt to fix the issue.
What I need to do is to somehow say that if an instance of the user control already exists, do not create a new instance of that object. Instead, just make the user control visible again. Therefore I have written code in an attempt to accomplish this purpose. When I select the item the first time, everything is fine. When I hide the user control and try to re-open it again nothing happens.
The following is the code I have written for this purpose which occurs upon the selection of the item from the tool-strip menu:
if (Controls.ContainsKey("CheckAvailUserControl"))
{
Controls["CheckAvailUserControl"].Dock = DockStyle.Fill;
Controls["CheckAvailUserControl"].Visible = true;
Controls["CheckAvailUserControl"].Show();
Controls["CheckAvailUserControl"].Refresh();
}
else
{
UserControl checkAvailUserControlLoad = new CheckAvailUserControl();
Controls.Add(checkAvailUserControlLoad);
checkAvailUserControlLoad.Dock = DockStyle.Fill;
checkAvailUserControlLoad.Visible = true;
checkAvailUserControlLoad.Show();
}
When I trace through my code in the debugger it is in fact hitting the right parts of the above if/else statement. Its just not displaying the user control on the screen the 2nd time I attempt to load it.
The question is: How do I get the user control to load correctly after I close it, then select it from the tool-strip menu again?
I think that Controls.ContainsKey(...) is always returning false, because you never assigned a name to your control when you created it.
If, when you create the control, you say
//...
checkAvailUserControlLoad.Name = "Something"
//...
Controls.Add(checkAvailUserControlLoad);
then
Controls.ContainsKey("Something")
will return true, and you'll be able to re-use the control by using Controls["Something"]
Here you go:
private void button_Click(object sender, EventArgs e)
{
// pass in the containing panel
LoadControl<MyControls.MyControl>(panelContainer);
}
void LoadControl<T>(Panel panel) where T : Control, new()
{
T _Control = GetControl<T>(panel);
if (_Control == null)
{
_Control = new T();
_Control.Dock = DockStyle.Fill;
panel.Controls.Add(_Control);
}
_Control.BringToFront();
}
T GetControl<T>(Panel panel) where T : Control
{
Type _Type = typeof(T);
String _Name = _Type.ToString();
if (!panel.Controls.ContainsKey(_Name))
return null;
T _Control = panel.Controls[_Name] as T;
return _Control;
}
This could work, but I think it's a little bit backwards: you're throwing new code at a problem that could be solved instead by moving your old code.
Instead, think about how the events work in your form. I bet that if you move your creating code to a slightly different event, or detect when the event is fired later and ignore those, you could fix the problem in a much nicer way.

Categories

Resources