Pages

Showing posts with label wpf. Show all posts
Showing posts with label wpf. Show all posts

Wednesday, October 22, 2014

The Simplest WPF Diagram Designer - Part 3

2 weeks ago I started a series of tips about WPF Diagram Designer (part1, part2). But as you know, there is nothing new under the sun. I have been pointed that there were few similar articles already posted on CodeProject so I`ve decided to post my next part of work here. I will add basic keyboard binding and RavenDB/XML save-load functionality. The source code can be found here: https://bitbucket.org/JleruOHeP/mywpfdesigner/src.

Keyboard commands
I have added 2 commands: Delete the current element and edit the text of the current element.
For the sake of simplicity I have changed the meaning of the Tag property for shapes. Now it stores the shape entity itself instead of just the Id. Because of that it became much faster to work with them. Because user can`t select the line we need to delete the connector, line and the opposite connector when someone selects the connector and presses Del. Now the Connector should know not only its host but as well its line. Ok. And the ChangeText command is really simple - as we already have all needed functionality.

RavenDB
I wanted to pick some embedded NoSQL database and the RavenDB was an obvious choice. I added wrapper class for that matter with a static EmbeddableDocumentStore DocumentStore and 2 methods - to Save the whole model or to load all objects of a specific type. With that implementation there is nothing complex about saving and for loading you have to clear the Canvas, reinitialize lines (because they do not store their coordinates - only connectors; connectors should be reconnected to their hosts), redraw the shapes, and finally recreate the Model class (assign proper commands and set it as a DataContext for a Window).

Also as the connectors are not used as a separate entity - I removed them from the model class. Now there are only 2 collections - for lines and shapes.

XML Serialization
It is really simple. Our Model class already has everything it needs - so we can easily use the XmlSerializer class.

 var serializer = new XmlSerializer(typeof(MyCanvasModel));
 using (var writer = new StreamWriter("data.xml"))
 {
  serializer.Serialize(writer, Model);
  writer.Close();
 }

The only thing we need to add - is [XmlIgnore] attribute for all the commands because they cant be serialized.

Sunday, September 7, 2014

WPF Save command

What can be easier then type

In WPF it is a designed way to interact with user. It is much simpler then binding to events as it gives you a better flexibility with CanExecute property and you code becomes reusable.

I wanted to implement that command and checked google. I was surprised to find that there were not any direct explanation in first 2-3 results. Even more, MSDN has a really bad explanation of what that command is and as an example there is a source code for Paste command. I am not sure why :)

So just in case - if you want to bind Save command you can CommandBinding to your own handlers like this:

XAML

    
        
        
    
    
        
        
    


C#
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void SaveCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true; //your logic
        }

        private void SaveCommand_OnExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("Save!");
        }

        private void OpenCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }

        private void OpenCommand_OnExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("Open");
        }
    }

Thursday, July 31, 2014

Elliptic context menu in WPF

Today I've decided to try build a simple elliptic context menu, something like in games. While you holding right mouse button you can pick something. As it is just a prototype I will build the simplest drawing application. With that menu user would be able to select a background color.

So, first thing first. Basic layout: just a canvas with a predefined background color and 2 events for right mouse up and down. OnDown should create context menu elements and OnUp should destroy them. And those elements should be created around mouse cursor.
The easiest way to create that circular shape is to use Path with ArcSegment. And the only trick here is trigonometry. We need to provide start and end points for the arc and they should be on a circle. Basically, x = cos and y = sin. Plus we should calculate the correct angle - in radians. Thats the all mathematics we will need!

Here is my method to create a menu segment:
private static Path CreateEllipticMenu(Point center, Brush color, int currentNumber, int maxNumber)
{
    var menu = new MyMenu {MyBrush = color};
    const int size = 50;
    var angleStart = currentNumber*2*Math.PI/maxNumber;
    var angleEnd = (currentNumber + 1)*2*Math.PI/maxNumber;

    var startX = center.X + size * Math.Cos(angleStart);
    var startY = center.Y + size * Math.Sin(angleStart);

    var endX = center.X + size * Math.Cos(angleEnd);
    var endY = center.Y + size * Math.Sin(angleEnd);

    var myPath = new Path();
    myPath.Stroke = menu.MyBrush;
    myPath.StrokeThickness = 20;
    myPath.HorizontalAlignment = HorizontalAlignment.Left;
    myPath.VerticalAlignment = VerticalAlignment.Center;
    var arc = new ArcSegment();
    arc.IsLargeArc = false;
    arc.Size = new Size(size, size);
    arc.SweepDirection = SweepDirection.Clockwise;
    arc.Point = new Point(endX, endY);
    var pathFigure = new PathFigure();
    pathFigure.StartPoint = new Point(startX, startY);
    pathFigure.Segments.Add(arc);
    myPath.Data = new PathGeometry(new List<PathFigure> {pathFigure});

    myPath.MouseEnter += (e, s) => { myPath.Stroke = Brushes.Red; menu.IsSelected = true; };
    myPath.MouseLeave += (e, s) => { myPath.Stroke = menu.MyBrush; menu.IsSelected = false; };

    myPath.Tag = menu;
    return myPath;
}

I have also defined mouse enter and mouse leave events so I can select active menu item and every menu item has MyMenu object associated with it. That class is just a helper:
public class MyMenu
{
    public Brush MyBrush { get; set; }
    public bool IsSelected { get; set; }
}

Well, thats all! Just as an addition here is an example of usage:

private void MyCanvas_OnMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
    var center = new Point(e.GetPosition(MyCanvas).X, e.GetPosition(MyCanvas).Y);

    MyCanvas.Children.Add(CreateEllipticMenu(center, Brushes.MediumSlateBlue, 0, 4));
    MyCanvas.Children.Add(CreateEllipticMenu(center, Brushes.MediumAquamarine, 1, 4));
    MyCanvas.Children.Add(CreateEllipticMenu(center, Brushes.MediumVioletRed, 2, 4));
    MyCanvas.Children.Add(CreateEllipticMenu(center, Brushes.LightYellow, 3, 4));
}

private void MyCanvas_OnMouseRightButtonUp(object sender, MouseButtonEventArgs e)
{
    int i = 0;
    while (i < MyCanvas.Children.Count)
    {
        var child = MyCanvas.Children[i];
        if (child is Path && (child as Path).Tag is MyMenu)
        {
            var menu = (child as Path).Tag as MyMenu;
            if (menu.IsSelected) MyCanvas.Background = menu.MyBrush;

            MyCanvas.Children.Remove(child as Path);
            continue;
        }
        i++;
    }
}

Friday, December 27, 2013

Binding enum to WPF combobox

Many times I found myself binding combobox to a set of values. A common scenario is that those values are just strings - representation of a application state, so creation of a static collection of strings was not very attractive for me. Enums are naturally fit in this scenario. But how to bind combobox to the enum? To comfortably bind enum its items should have descriptions - they will be used as a values for combobox. But here is a small helper to do this:
public static class EnumHelper
{
  public static string Description(this Enum eValue)
  {
    var nAttributes = eValue.GetType().GetField(eValue.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);

    if (!nAttributes.Any())
      return eValue.ToString();

    var descriptionAttribute = nAttributes.First() as DescriptionAttribute;
    if (descriptionAttribute != null)
      return descriptionAttribute.Description;

    return eValue.ToString();
  }

  public static IEnumerable<KeyValuePair<Enum, string>> GetAllValuesAndDescriptions<TEnum>() where TEnum : struct, IConvertible, IComparable, IFormattable
  {
    if (!typeof(TEnum).IsEnum)
      throw new ArgumentException("TEnum must be an Enumeration type");

    return from e in Enum.GetValues(typeof(TEnum)).Cast<Enum>()
           select new KeyValuePair<Enum, string>(e, e.Description());
  }
}


public enum WorkModes
{
  [Description("Simple mode")]
  Simple,
  [Description("Complex mode")]
  Complex,
  [Description("Third (optional) option")]
  Etc
}

//from windows loaded event - bind itemsSource of combobox to enum.
//and set it to default value
private void Window_Loaded(object sender, RoutedEventArgs e)
{
  cbCurrentState.ItemsSource = EnumHelper.GetAllValuesAndDescriptions();
  cbCurrentState.SelectedIndex = 0;
}

//And combo box declaration: