Orc.Memento Global Undo with Multiple Controls - c#

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).

Related

Creating layout & logic AutoCAD plugin dialog

Brand new to AutoCAD plugin development. I'm trying to create a plugin that loads as an entire main menu option inside of AutoCAD (let's call this menu the "Fizzbuzz" menu, and when the user selects one of the menu items (say, Fizzbuzz >> Foobar) I want a simple dialog/window to show up on screen in the top-left corner of AutoCAD.
I'm trying to figure out where the presentation/layout logic for this dialog/popup window needs to go (what file does it live in and how do I create/edit it?), and just as importantly: where the event-driven GUI logic needs to go (again: what file do I edit and in what language?). By "GUI logic" I mean: let's say there's a checkbox or button inside my dialog...when the user clicks/interacts with these UI components, I need custom logic to execute.
Any idea what files house this type of presentation/GUI logic for new AutoCAD plugins and how I create/edit them? Thanks in advance!
I have added a palette hosting a winform control this way:
using System.Windows.Forms;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Windows;
namespace AMU.AutoCAD.BlockTool
{
public class MyPalette : IExtensionApplication
{
private PaletteSet palette;
private Control paletteControl;
public void Initialize()
{
//This is called when AutoCAD loads your assembly
this.palette = new PaletteSet("Name")
{
TitleBarLocation = PaletteSetTitleBarLocation.Left,
Style = PaletteSetStyles.Snappable //Your Styles
};
this.paletteControl = new Control(); //Instance of your Control that will be visible in AutoCAD
this.palette.Add("HEADER", this.paletteControl);
this.palette.Visible = true;
}
public void Terminate()
{
//cleanup
this.palette.Dispose();
this.paletteControl.Dispose();
}
}
}
By providing a class implementing the IExtensionApplication you can execute custom code on loading a dll, without explicitly call a method. You now can create a so called PaletteSet and add a Winform or WPF Control to it.

Focus abstraction

UserControl with buttons (some of them are disabled) is nested inside other UserControl. There are several of such displayed in the window at once.
Now I need to set focus to first enabled button of nested UserControl, while the logic to choose focus will run on the level of window (e.g. when window will enable certain UserControl).
I need to be able to pass that focus request (via properties?) through several ViewModels and finally trigger it in the View of nested UserControl.
Ho can I abstract focus request? E.g. I want to be able to tell "set focus to this high level UserControl" and that should somehow automatically go through nested UserControl and its buttons, because only button is the element what can receive focus.
Pseudo-code:
// in window
UserControlA.Focus();
// should in fact set focus to 4th button of nested user control
UserControlA.UserControlB.ButtonD.Focus();
// because of data templates it is actually more like this
var nested = UserControlA.ContentControl.Content as UserControlB;
var firstEnabledButton = nested.ItemsControl[3] as Button;
firstEnabledButton.SetFocus();
// and because of MVVM it may be as simple as
ViewModelA.IsFocused = true;
// but then A should run
ViewModelB.IsFocused = true;
// and then B should set property of button ViewModel
Buttons.First(o => o.IsEnabled).IsFocused = true.
// and then this has to be somehow used by the view (UserControlB) to set focus...
Problem is not with how to set focus in MVVM, this can be done somehow (with triggers it needs ugly workaround where property is first set to false). My problem is how to pass that request ("and then ..., and then ..., and then..." in example above).
Any ideas?
I am looking for a simple and intuitive xaml solution with the most reusability. I don't want to spam every ViewModel and views with ...IsFocused properties and bindings.
I can use some side effect to my advantage, e.g. consider this behavior
public static bool GetFocusWhenEnabled(DependencyObject obj) => (bool)obj.GetValue(FocusWhenEnabledProperty);
public static void SetFocusWhenEnabled(DependencyObject obj, bool value) => obj.SetValue(FocusWhenEnabledProperty, value);
public static readonly DependencyProperty FocusWhenEnabledProperty =
DependencyProperty.RegisterAttached("FocusWhenEnabled", typeof(bool), typeof(FocusBehavior), new PropertyMetadata(false, (d, e) =>
{
var element = d as UIElement;
if (element == null)
throw new ArgumentException("Only used with UIElement");
if ((bool)e.NewValue)
element.IsEnabledChanged += FocusWhenEnabled_IsEnabledChanged;
else
element.IsEnabledChanged -= FocusWhenEnabled_IsEnabledChanged;
}));
static void FocusWhenEnabled_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var element = (UIElement)sender;
if (element.IsEnabled)
element.Dispatcher.InvokeAsync(() => element.Focus()); // invoke is a must
}
which can be used to automatically focus enabled element. This require some IsEnabled logic in addition and will easily stop working in some complicated scenarios (where enabling should not cause the focusing).
I am thinking if I can add some attached property to pass focus requests all the way through xaml (using only xaml) when attempting to set focus to container, which is not focusable.
I think you should consider using the FrameworkElement.MoveFocus method together with FocusNavigationDirection.Next - this should in general give you the expected result, i.e. give focus to the first encountered control which can receive keyboard focus. In particular that means that non-focusable controls, disabled controls, and controls that cannot receive keyboard focus (such as ItemsControl, UserControl etc.) will be omitted. The only catch here is that the controls will be traversed in tab order, but unless you're messing around with that it should traverse the visual tree in depth-first pre-order manner. So this code:
UserControlA.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
should give focus to UserControlA.UserControlB.ButtonD if it is the first keyboard-focusable and enabled descendant of UserControlA.
In terms of dismissing the necessity to use code-behind what I'd do is the following. First of all I'd drop using view-model properties to control focus. Moving focus seems to me a lot more like request-based concept rather than state-based, so I'd use events (e.g. FocusRequested) instead. To make it reusable I'd create a one-event interface (e.g. IRequestFocus). The final touch would be to create a behavior that would automatically inspect if DataContext of the attached object implements IRequestFocus and call MoveFocus each time the FocusRequested event is raised.
With such setup all you'd need to do is to implement IRequestFocus in ViewModelA, and attach the behavior to UserControlA. Then simply raising the FocusRequested in ViewModelA would result in moving focus to UserControlA.UserControlB.ButtonD.

custom user control

I have designed custom user control in c# .This user control is include :
textbox,check box,button.
Now I want to consume designed user control in my project but the problem is I can't access to the textbox,checkbox,button EVENTS when consume user control in my form and there are only EVENTS for user control.how can I make it possible that each object events become accessible when consuming designed user control ?
In your user control set your control like "text-box" Modifiers property to Public.so when you add this user control to your form. you can access to your text box evens:
public Form1()
{
InitializeComponent();
userControl11.textBox1.TextChanged += new EventHandler(textBox1_TextChanged);
}
void textBox1_TextChanged(object sender, EventArgs e)
{
MessageBox.Show("user control textbox.text changed");
}
you might need to manually create public events for the events of those controls which you want to be accessible from outside.
another way is, when initializing those controls in you user control, using public instead of private (which is automatically generated by VS), these code should be located in the xxx.Designer.cs, and they looks likeprivate System.Windows.Forms.Button button1. Then it can be accessed through MyUserControl.button1. But doing so, the entire control will be accessible from outside of your user control, which does not feel very well personally.
I think you should add public events to your custom control class, and make subscribing and unsubscribing there. Just like that:
public event EventHandler ComboboxClick
{
add { _combobox.Click += value; }
remove { _combobox.Click -= value; }
}
For more information see http://msdn.microsoft.com/en-us/library/8627sbea(v=vs.71).aspx
Edit: I would not recomend setting inner controls of your custom control as public properties, because it's a violation of encapsulation principle. You design your own control to do some specific job and clients of the control should stay unaware of its inner composition. Should you change the inner composition of your control in future (switch to some 3rd party textbox control, for example), you would only need to make changes in your custom control class, and its clients would still work properly as if nothing has happend. Here's a good koan about encapsulation =)

How do I handle Login/Logout in Caliburn.Micro?

I'm new to Caliburn.Micro and I'm wondering what is the best way to handle user Login/Logout cycles in my application. I saw some suggestions online to implement this using an empty Shell-View which switches between the LoginView and the main application view, each with a custom ViewModel of course.
I don't really like this solution, because for me these are 2 separate windows with very different properties (Title, Icon, Size) and it seems an unclean solution two change one window to look like the other. Another problem is, that the Login Window comes from an utility library which I don't control and which doesn't use Caliburn.Micro, it's a plain old Window which gives me an event when the user clicks "Login".
I also saw suggestions to display this Dialog in the Bootstrapper startup method, but the problem I see with that is that the user can choose to "Logout" of the application which should display the Login dialog again. It seems wrong to me to handle the switching between the Views in the Bootstrapper.
What I would like is to have some sort of ApplicationViewModel or ApplicationController which works like a Caliburn Conductor, but instead of switching between Views inside a Window, it should switch between the LoginWindow and the MainWindow and should also handle Closing of the whole application (which also requires a Logout). On Activation it would show the LoginWindow, handle the Login event and then switch to the Main Window (Shell). If the user chooses to "LogOut", the event should bubble up to the ApplicationViewModel/Controller again which would deactivate/close the MainWindow, perform the Logout and then show the LoginDialog again. Similar a Close event would do the Logout, but then Shutdown the whole application.
So my questions are:
What do you think about this solution and do you have another/better one?
How do I implement this? ;-)
Thanks a lot!
I think the solution to your problem is fairly easy.
In a nutshell you are creating one ViewModel as Shell which is represented with a Login Window when the application starts. If the user logs in successfully this window closes and the same instance of the viewModel is displayed in a Content Window. If the user is doing a logout, the Login Window is shown again.
First of all create an interface IShell which exposes two delegates LoginSuccessful and Logout
public interface IShell
{
Action LoginSuccessful { get; set; }
Action Logout { get; set; }
}
Next create a class ShellViewModel which implements IShell
public class ShellViewModel : Screen, IShell
{
public ShellViewModel()
{
LoginSuccessful = delegate { };
Logout = delegate { };
}
public Action LoginSuccessful { get; set; }
public Action Logout { get; set; }
public void DoLogin()
{
LoginSuccessful();
}
public void DoLogout()
{
Logout();
}
}
The methods DoLogin and DoLogout are Actions which can be bound to a Button or whatever control appropriate for you.
Next step is to override the OnStartupMethod in your Bootstrapper. This premises that you have an instance of the WindowManager and ShellViewModel exported by an IoC Framework of your choice.
protected override void OnStartup(object sender, StartupEventArgs e)
{
var windowManager = IoC.Get<IWindowManager>();
var viewModel = IoC.Get<IShell>();
viewModel.LoginSuccessful =
() => GuardCloseAndReopen("Content");
viewModel.Logout =
() => GuardCloseAndReopen("Login");
windowManager.ShowWindow(viewModel, "Login");
}
private void GuardCloseAndReopen(string shellViewMode)
{
var windowManager = IoC.Get<IWindowManager>();
var shellScreen = IoC.Get<IShell>() as Screen;
Application.ShutdownMode = ShutdownMode.OnExplicitShutdown;
shellScreen.TryClose();
Application.ShutdownMode = ShutdownMode.OnLastWindowClose;
windowManager.ShowWindow(shellScreen, shellViewMode);
}
The trick to this is: If the DoLogout method is called, the current window gets closed by calling TryClose on the ShellViewModel. At the same time you prevent the application from being shutdown by setting the Application.ShutdownMode to OnExplicitShutdown. Then using the windowmanager, you create another window in Login Mode by passing "Login" as Context information to the windowManager. This is actually the same ViewModel, however, with a different visual representation.
For Logout you are doing the same thing just around.
To get this working using Caliburn Conventions, you need a special project structure as seen here (and explained there):
Now I challenge you to take this code and create a little sample application. Create a Login View (which does Login with a Button or whatever) and create a Content View with a Logout Button using the LoginSuccessful/ Logout Methods.
This will solve your issue with a minimum of Code and classes. Hope this will be helpful to you.
I've had a go at creating something that basically works but probably needs a bit more work to be really usable. The fully comments and source can be found on this post Caliburn.Micro Login Window sample on my website.
I used the IEventAggregator of Caliburn.Micro to control the transition between the two windows. You get this code to open the login screen:
public void Handle(LoginEvent message)
{
LoginWindow loginWindow = new LoginWindow();
loginWindow.Login += new EventHandler<LoginEventArgs>(this.LoginWindow_Login);
loginWindow.Cancel += new EventHandler(LoginWindow_Cancel);
loginWindow.ShowDialog();
}
this same source is used for both the first time the app opens and when the Logout event is published. the Logout event looks like this:
public void Handle(LogoutEvent message)
{
Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
message.Source.TryClose();
Application.Current.ShutdownMode = ShutdownMode.OnLastWindowClose;
this.events.Publish(new LoginEvent());
}
When a login is successful it uses this code to open the main window which is based on a ViewModel:
ContentViewModel viewModel;
viewModel = IoC.Get<ContentViewModel>();
viewModel.Username = e.Username;
this.windowManager.ShowWindow(viewModel);

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

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.

Categories

Resources