undo/redo command stack for InkCanvas - wpf

I am creating paint like application using InkCanvas , I am willing to implement Undo
and Redo functionality in my application .
Which is the best way to implement Undo / Redo for InkCanvas ??

I've implemented undo / redo for a WPF application and ended up publishing my undo / redo code to http://muf.codeplex.com/. You can also get it via NuGet. Just look for "MUF" or "Monitored Undo Framework". It includes support for Silverlight 4.0, as well as .NET 3.5, 4.0, and WP7.
In my WPF app, we also had an InkCanvas that supported Undo / Redo. In my case, the strokes for the InkCanvas were saved to a database with the rest of the data. I hooked the various events on InkCanvas to detect when the strokes had changed. Then used these events to update the entities.
The entities tracked the changes to the strokes and integrated into the Undo / Redo library. When the user clicked Undo, the library would alter the entities back to their original state. Then I'd push those strokes back into the InkCanvas and trigger a layout update.
Comments and questions are welcome on the codeplex site ( http://muf.codeplex.com/ ). You'll also find complete documentation and sample apps there.

I know its too late but if someone is here for InkCanvas only than this answer might help:
public partial class MainWindow : Window
{
System.Windows.Ink.StrokeCollection _added;
System.Windows.Ink.StrokeCollection _removed;
private bool handle = true;
public MainWindow()
{
InitializeComponent();
inkCanvas1.Strokes.StrokesChanged += Strokes_StrokesChanged;
}
private void Strokes_StrokesChanged(object sender, System.Windows.Ink.StrokeCollectionChangedEventArgs e)
{
if(handle)
{
_added = e.Added;
_removed = e.Removed;
}
}
private void Undo(object sender, RoutedEventArgs e)
{
handle = false;
inkCanvas1.Strokes.Remove(_added);
inkCanvas1.Strokes.Add(_removed);
handle = true;
}
private void Redo(object sender, RoutedEventArgs e)
{
handle = false;
inkCanvas1.Strokes.Add(_added);
inkCanvas1.Strokes.Remove(_removed);
handle = true;
}
}
And in XAML:
<InkCanvas x:Name="inkCanvas1" Width="100" Height="100" Background="Yellow"/>
<Button Content="Undo" Click="Undo" />
<Button Content="Redo" Click="Redo"/>

I don't know if this helps.. But one very easy way to UNDO is:
YourWindow.xaml.cs
private void Undo_Click(object sender, RoutedEventArgs e)
{
if (YourInkCanva.Strokes.Count > 0)
{
YourInkCanva.Strokes.RemoveAt(YourInkCanva.Strokes.Count - 1);
}
else
{
// Else Do Nothing.
}
}
You would want to replace YourInkCanva with the name of your inkcanva.

Related

Where is ExecWB?

If I use WPF syntax:
<WebBrowser Grid.Row="1" Grid.Column="1" x:Name="htmlView"></WebBrowser>
Or if I add a WindowsFormsHost:
<WindowsFormsHost x:Name="formsHost" Grid.Row="1" Grid.Column="1">
</WindowsFormsHost>
And in the CS file:
public partial class MainWindow : Window
{
System.Windows.Forms.WebBrowser htmlView= new System.Windows.Forms.WebBrowser();
public MainWindow()
{
InitializeComponent();
formsHost.Child = htmlView;
htmlView.Navigate("d:\\test.xml");
}
private void menuFilePageSetup_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Do do");
}
}
Either way, the ExecWB method is not exposed. So I can't port this code over from my MFC C++ CHtmlView derived class:
ExecWB(OLECMDID_PAGESETUP, OLECMDEXECOPT_DONTPROMPTUSER, NULL, NULL);
Am I missing something? At this point in time I can't implement my print preview / page setup / zoom functionality because I can't use ExecWB.
I have tried deriving my own class from the Winforms browser but it is still not listed.
Thank you.
I don't understand why in this question:
How do I programmatically change printer settings with the WebBrowser control?
It refers to ShowPrintDialog but even that is not listed.
Partial success! I don't know why, but now, when I use the hosted Winforms version, I can atleast see some of the methods:
public partial class MainWindow : Window
{
System.Windows.Forms.WebBrowser htmlView= new System.Windows.Forms.WebBrowser();
public MainWindow()
{
InitializeComponent();
formsHost.Child = htmlView;
htmlView.Navigate("d:\\test.xml");
}
private void menuFilePageSetup_Click(object sender, RoutedEventArgs e)
{
htmlView.ShowPageSetupDialog();
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
htmlView.ShowPrintPreviewDialog();
}
}
But ExecWb is still missing for managing things like zooming. I found this:
How do I print from the wpf WebBrowser available in .net 3.5 SP1?
One of the answers:
mshtml.IHTMLDocument2 doc = webBrowser.Document as mshtml.IHTMLDocument2;
doc.execCommand("Print", true, null);
But even if I add a reference to the mshtml library the compiler will not let me convert from HtmlDocument to IHtmlDocument2. Eventually I got this:
Clearing the selection in a webbrowser control
So now I know how to select all and copy to clipboard again (as long as I use the Winforms edition). However, having looked here:
https://msdn.microsoft.com/en-us/library/ms533049(v=vs.85).aspx
There seems to be no match for Optical Zoom:
ExecWB(OLECMDID_OPTICAL_ZOOM, OLECMDEXECOPT_DONTPROMPTUSER, &vZoom, NULL);
The basic answer for calling ExecWb is to do this:
Use the WinForms WebBrowser control
Then something like htmlView.Document.ExecCommand
Pass in commands like:
SelectAll
Copy
for clipboard items.
There are native methods for displaying the print dialogues etc..
Only thing that does not seem to be supported is:
ExecWB(OLECMDID_OPTICAL_ZOOM, OLECMDEXECOPT_DONTPROMPTUSER, &vZoom, NULL);

WPF Richtextbox Bindable in .net 4.5

So I'm trying to use David Veeneman's Bindable WPF RichTextBox here in my .net 4.5 project. After adding the control and the ValueConverter in my code I noticed only the the public object Convert() will be triggered but the public object ConvertBack() not.
After reading the comments to this project I changed following parts of the control source code.
private static void OnDocumentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var thisControl = (EcoRichTextBox)d;
if (thisControl.m_InternalUpdatePending > 0)
{
thisControl.m_InternalUpdatePending--;
return;
}
// Changed:
try
{
thisControl.TextBox.Document = (e.NewValue == null) ? new FlowDocument() : (FlowDocument)e.NewValue;
}
catch { }
thisControl.m_TextHasChanged = false;
}
And this Event Handler:
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
// Set the TextChanged flag
m_TextHasChanged = true;
// Changed:
Document = TextBox.Document;
}
Now the the both method of the ValueConverter worked fine but events like private void OnNormalTextClick(object sender, RoutedEventArgs e) causes a FatalExecutionEngineError on Runtime.
So i wonder if there are major changes form WPF 3.5 to 4.5?
Or anybody have an idea to work around this?
Update
Binding in XAML
<uc:FsRichTextBox Margin="5"
Document="{Binding Path=Ereignis.Bericht,
Converter={StaticResource flowDocumentConverter},
UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}" />
I ran the demo you linked here in VS2015 with target framework 4.0 and 4.5. It will not update when I take out the two way data binding.
Add to your RTB. Two way data binding and a name:
Mode=TwoWay
x:Name="EditBox"
I think rather than managing the text change yourself here, remove this:
// Changed:
Document = TextBox.Document;
Use an event handler to update the data.
Then in your event handler that is managing your updates (I am assuming a button click? And allow this to manage the update.
this.EditBox.UpdateDocumentBindings();
The x:name attribute is valuable.
This is all found in the source code.
If you can be more clear about how your project is arranged I can provide more detail. But for starters, I would do this. Stick more closely to the provided example.

drag and drop in wpf user control

I had created a drag and drop control in wpf to drag and drop data between two list boxes which worked as a charm until I moved it to another project.
The difference being It was initially a wpf window and used the window object to get the mouse position and the position of the controls inside.
this.topWindow = Window.GetWindow(this.sourceItemsControl); //Source items control is instance of ItemsControl
bool previousAllowDrop = this.topWindow.AllowDrop;
this.topWindow.AllowDrop = true;
and now I had to change it to a user control instead, since its a part of a bigger project which is a Windows forms project and the views are linked as a Smart Part from the main project. So now the Window object is null.
I looked for a similar functionality for User Control but could not find it..What is it that I am missing out?? I know there should be something..Would appreciate any help on the same..
P.S. : I am using the MVVM architecture
Found the way to find the base User control using recursion, Thanks to ekholm for the heads up..
public static UserControl FindParentControl(DependencyObject child)
{
DependencyObject parent = VisualTreeHelper.GetParent(child);
//CHeck if this is the end of the tree
if (parent == null) return null;
UserControl parentControl = parent as UserControl;
if (parentControl != null)
{
return parentControl;
}
else
{
//use recursion until it reaches a Window
return FindParentControl(parent);
}
}
Now this base user control can be used to find the coordinates (reference) as well as setting other properties like AllowDrop, DragEnter, DragOver etc.
If you need MVVM, than you may examine this solution:
in your .xaml file add:
<ContentControl Content="{Binding Content, Mode=TwoWay}" AllowDrop="True" Name="myDesignerContentControl" />
Than in your ViewModel add the following:
private Panel _content;
public Panel Content {
get { return _content; }
set {
_content = value;
if (_content != null) {
RegisterDragAndDrop();
}
base.RaisePropertyChanged("Content");
}
}
private void RegisterDragAndDrop() {
Content.Drop += OnDrop;
Content.PreviewMouseLeftButtonDown += OnMouseLeftButtonDown;
Content.PreviewDragOver += OnDragOver;
}
private void OnDesignerDrop(object sender, DragEventArgs e) {
//some custom logic handling
}
private void OnDesignerMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
var control = (FrameworkElement)e.Source;
//some custom logic handling for doing drag & drop
}
private void OnDesignerDragOver(object sender, DragEventArgs e) {
//some custom logic handling for doing drag over
}
The idea is that you should use controls instead of mouse positions, it will be more simple and logical approach. The code above is an example of approach to use in MVVM for having an content area on which you may perform drag and drop of some controls. The idea behind is also suitable for drag and drop data between two list boxes, that you may have on the same content area.
Hope this helps.

The best approach to show/hide windows indpendently

I am a student and am building a C# WPF application. It has three windows:
Sign in window
Create account window
and Main application window.
I uploaded a figure to show the type of navigation I am trying to implement:
I do not think it is correct to make a window show up/hide inside the close/load event of another window.
Can someone show me the right way to implement this navigation?
Also, is it a good practice to make the three windows private properties of the application class?
The last window has a frame control to support page navigation. Again, is it better to make the three pages private properties of MainWindow application?
I am sorry if this is so obvious or easy to do.
Thanks
I would not have the three windows as properties of the application. I'd instanciate a copy of the sign-in window and use that as my central point of control.
When the user logs in, hide the sign in window, show a new main window and add a hook on the main windows Closed event.
e.g
if (logonSuccess)
{
var mainWindow = new MainWindow();
mainWindow.Closed += ReshowSignupWindow;
}
I'd also have the sign-in window do the same for the create account window. Thus, I'd have the create account window return to the signup window which would either reshow itself or start the main window if an account was created.
e.g.:
// In sign-in window, handle the create window being closed
private void CreateWindowClosedHandler(object sender, EventArgs e)
{
if (accountCreatedOK)
{
ShowMainWindow();
}
else
{
ReshowSignupWindow();
}
}
I'd probably look at having the create account window shown as a dialog window via a call to ShowDialog().
Hope that helps...
Something like this code might do it (untested, I just typed it in visual studio to autoformat the code)
The XAML is for the Login Dialog. The RegistrationDialog should be similar, except for the button and handler for the registration Button.
<Window x:Class="WpfApplication1.LoginWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="LoginWindow">
<StackPanel>
<Button IsDefault="True" Content="Submit" Click="SubmitButton_Click"/>
<Button IsCancel="True" Content="Cancel" />
<Button Content="CreateAccount" Click="CreateAccountButton_Click"/>
</StackPanel>
</Window>
//Handler of LoginWindow and RegistrationWindow
private void SubmitButton_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = true;
}
//Handler of LoginWindow only
private void CreateAccountButton_Click(object sender, RoutedEventArgs e)
{
this.IsCreatingAccount = true;
this.DialogResult = false;
}
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
bool isCanceled;
while (loginWin.ShowDialog() == false && !isCanceled)
{
if (loginWin.IsAccountCreationRequested)
{
if (registrationWin.ShowDialog())
{
isCanceled = true;
}
else
{
loginWin.IsAccountCreationRequested = false;
}
}
else
{
isCanceled = true;
}
}
if (loginWin.DialogResult) MainWindow.Show();
}
}
I am currently working on a Silverlight Application which is more or less similar to your application. What i feel is your can have 2 xaml controls(one for Login and other for your main application). For create account, you can use a child window which will be called from login control. And use a TabControl in your main application which will hold your other 3 xaml controls(Page1.xaml, Page2.xaml and Page3.xaml). Feel free to ask if you have any issues.
Dont forget to mark my reply as answer if it solves your problem.
I suggest you to follow the pattern. Your logic looks tightly binding with UI(user Interface) and logics.
The best pattern i like is for WPF or Silverlight is MVVM(Model View View Model). There are lot of Examples available in google for MVVM.
Just put a glance in anyone MVVM example you will be clear in developing WPF or Silverlight app.
some links are below,
http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
http://www.c-sharpcorner.com/UploadFile/raj1979/simple-mvvm-pattern-in-wpf/
http://www.codeproject.com/Articles/126249/MVVM-Pattern-in-WPF-A-Simple-Tutorial-for-Absolute

WPF Mousedown => No MouseLeave Event

I'm building a Windows Presentation Foundation control with Microsoft Blend.
When I leave my control by pressing the left-mouse-button, the MouseLeave-Event is not raised. Why not?
This is intended behaviour: When you are doing mousedown on a control and leaving the control, the control STILL retains its "capture" on the mouse, meaning the control won't fire the MouseLeave-Event. The Mouse-Leave Event instead will be fired, once the Mousebutton is released outside of the control.
To avoid this, you can simple tell your control NOT to capture the mouse at all:
private void ControlMouseDown(System.Object sender, System.Windows.Forms.MouseEventArgs e)
{
Control control = (Control) sender;
control.Capture = false; //release capture.
}
Now the MouseLeave Event will be fired even when moving out while a button is pressed.
If you need the Capture INSIDE the Control, you need to put in more effort:
Start tracking the mouseposition manually, when the mousekey is pressed
Compare the position with the Top, Left and Size Attributes of the control in question.
Decide whether you need to stop the control capturing your mouse or not.
public partial class Form1 : Form
{
private Point point;
private Boolean myCapture = false;
public Form1()
{
InitializeComponent();
}
private void button1_MouseDown(object sender, MouseEventArgs e)
{
myCapture = true;
}
private void button1_MouseMove(object sender, MouseEventArgs e)
{
if (myCapture)
{
point = Cursor.Position;
if (!(point.X > button1.Left && point.X < button1.Left + button1.Size.Width && point.Y > button1.Top && point.Y < button1.Top + button1.Size.Height))
{
button1.Capture = false; //this will release the capture and trigger the MouseLeave event immediately.
myCapture = false;
}
}
}
private void button1_MouseLeave(object sender, EventArgs e)
{
MessageBox.Show("Mouse leaving");
}
}
of course you need to stop the own tracking ( myCapture=false;) on MouseUp. Forgot that one :)
When I don't get mouse events I expect I typically use Snoop to help me understand what is happening.
Here are a couple of links:
1- Snoop (a WPF utility)
2- CodePlex project for Snoop
And for completeness and historical reasons (not the bounty - it doesn't make sense having two duplicate questions - you should probably move it into one if not too late)...
I made a thorough solution using global mouse hook here (approach 2)
WPF: mouse leave event doesn't trigger with mouse down
And simplified its use - you can use it by binding to commands in your view-model - e.g.
my:Hooks.EnterCommand="{Binding EnterCommand}"
my:Hooks.LeaveCommand="{Binding LeaveCommand}"
my:Hooks.MouseMoveCommand="{Binding MoveCommand}"
...more details in there
Old question but I came across the same problem with a Button (MouseLeave does not fire while MouseDown because MouseDown Captures the Mouse...)
This is how I solved it anyway:
element.GotMouseCapture += element_MouseCaptured;
static void element_MouseCaptured(object sender, MouseEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
element.ReleaseMouseCapture();
}
Hope that helps someone looking for a quick fix :P

Resources