How can I create a tagging control similar to evernote in wpf?

前端 未结 2 1484
暗喜
暗喜 2020-12-07 15:07

I like the tagging control in Evernote (windows version) and was wondering if there is something similar out there? I have only been able to find tag cloud controls.

2条回答
  •  情话喂你
    2020-12-07 15:16

    This seemed like a really nice exercise, so I tried to build this control. I didn't test it thoroughly, let me know if you want to work with it and need further help.

    enter image description here

    enter image description here

    enter image description here

    enter image description here

    Example usage:

    
    
        
            
        
    
        
            
            
                
                    read
                    receipt
                    recipe
                    research
                    restaurants
                
            
        
    
    

    ViewModel:

    using System.Collections.Generic;
    using System.ComponentModel;
    
    namespace WpfApplication1
    {
        public class ViewModel : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;
    
            private List _selectedTags = new List();
            public List SelectedTags
            {
                get { return _selectedTags; }
                set
                {
                    _selectedTags = value;
                    if (_selectedTags != value)
                        OnPropertyChanged("SelectedTags");
                }
            }
    
            public ViewModel()
            {
                this.SelectedTags = new List() { new EvernoteTagItem("news"), new EvernoteTagItem("priority") };
            }
    
            private void OnPropertyChanged(string propertyName)
            {
                if (this.PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
    

    EvernoteTagControl:

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Windows;
    using System.Windows.Controls;
    
    namespace WpfApplication1
    {
        [TemplatePart(Name = "PART_CreateTagButton", Type = typeof(Button))]
        public class EvernoteTagControl : ListBox
        {
            public event EventHandler TagClick;
            public event EventHandler TagAdded;
            public event EventHandler TagRemoved;
    
            static EvernoteTagControl()
            {
                // lookless control, get default style from generic.xaml
                DefaultStyleKeyProperty.OverrideMetadata(typeof(EvernoteTagControl), new FrameworkPropertyMetadata(typeof(EvernoteTagControl)));
            }
    
            public EvernoteTagControl()
            {
                //// some dummy data, this needs to be provided by user
                //this.ItemsSource = new List() { new EvernoteTagItem("receipt"), new EvernoteTagItem("restaurant") };
                //this.AllTags = new List() { "recipe", "red" };
            }
    
            // AllTags
            public List AllTags { get { return (List)GetValue(AllTagsProperty); } set { SetValue(AllTagsProperty, value); } }
            public static readonly DependencyProperty AllTagsProperty = DependencyProperty.Register("AllTags", typeof(List), typeof(EvernoteTagControl), new PropertyMetadata(new List()));
    
    
            // IsEditing, readonly
            public bool IsEditing { get { return (bool)GetValue(IsEditingProperty); } internal set { SetValue(IsEditingPropertyKey, value); } }
            private static readonly DependencyPropertyKey IsEditingPropertyKey = DependencyProperty.RegisterReadOnly("IsEditing", typeof(bool), typeof(EvernoteTagControl), new FrameworkPropertyMetadata(false));
            public static readonly DependencyProperty IsEditingProperty = IsEditingPropertyKey.DependencyProperty;
    
            public override void OnApplyTemplate()
            {
                Button createBtn = this.GetTemplateChild("PART_CreateTagButton") as Button;
                if (createBtn != null)
                    createBtn.Click += createBtn_Click;
    
                base.OnApplyTemplate();
            }
    
            /// 
            /// Executed when create new tag button is clicked.
            /// Adds an EvernoteTagItem to the collection and puts it in edit mode.
            /// 
            void createBtn_Click(object sender, RoutedEventArgs e)
            {
                var newItem = new EvernoteTagItem() { IsEditing = true };
                AddTag(newItem);
                this.SelectedItem = newItem;
                this.IsEditing = true;
    
            }
    
            /// 
            /// Adds a tag to the collection
            /// 
            internal void AddTag(EvernoteTagItem tag)
            {
                if (this.ItemsSource == null)
                    this.ItemsSource = new List();
    
                ((IList)this.ItemsSource).Add(tag); // assume IList for convenience
                this.Items.Refresh();
    
                if (TagAdded != null)
                    TagAdded(this, new EvernoteTagEventArgs(tag));
            }
    
            /// 
            /// Removes a tag from the collection
            /// 
            internal void RemoveTag(EvernoteTagItem tag, bool cancelEvent = false)
            {
                if (this.ItemsSource != null)
                {
                    ((IList)this.ItemsSource).Remove(tag); // assume IList for convenience
                    this.Items.Refresh();
    
                    if (TagRemoved != null && !cancelEvent)
                        TagRemoved(this, new EvernoteTagEventArgs(tag));
                }
            }
    
    
            /// 
            /// Raises the TagClick event
            /// 
            internal void RaiseTagClick(EvernoteTagItem tag)
            {
                if (this.TagClick != null)
                    TagClick(this, new EvernoteTagEventArgs(tag));
            }
        }
    
        public class EvernoteTagEventArgs : EventArgs
        {
            public EvernoteTagItem Item { get; set; }
    
            public EvernoteTagEventArgs(EvernoteTagItem item)
            {
                this.Item = item;
            }
        }
    }
    

    EvernoteTagItem:

    using System.Collections;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    using System.Windows.Media;
    
    namespace WpfApplication1
    {
        [TemplatePart(Name = "PART_InputBox", Type = typeof(AutoCompleteBox))]
        [TemplatePart(Name = "PART_DeleteTagButton", Type = typeof(Button))]
        [TemplatePart(Name = "PART_TagButton", Type = typeof(Button))]
        public class EvernoteTagItem : Control
        {
    
            static EvernoteTagItem()
            {
                // lookless control, get default style from generic.xaml
                DefaultStyleKeyProperty.OverrideMetadata(typeof(EvernoteTagItem), new FrameworkPropertyMetadata(typeof(EvernoteTagItem)));
            }
    
            public EvernoteTagItem() { }
            public EvernoteTagItem(string text)
                : this()
            {
                this.Text = text;
            }
    
            // Text
            public string Text { get { return (string)GetValue(TextProperty); } set { SetValue(TextProperty, value); } }
            public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(EvernoteTagItem), new PropertyMetadata(null));
    
            // IsEditing, readonly
            public bool IsEditing { get { return (bool)GetValue(IsEditingProperty); } internal set { SetValue(IsEditingPropertyKey, value); } }
            private static readonly DependencyPropertyKey IsEditingPropertyKey = DependencyProperty.RegisterReadOnly("IsEditing", typeof(bool), typeof(EvernoteTagItem), new FrameworkPropertyMetadata(false));
            public static readonly DependencyProperty IsEditingProperty = IsEditingPropertyKey.DependencyProperty;
    
            /// 
            /// Wires up delete button click and focus lost 
            /// 
            public override void OnApplyTemplate()
            {
                AutoCompleteBox inputBox = this.GetTemplateChild("PART_InputBox") as AutoCompleteBox;
                if (inputBox != null)
                {
                    inputBox.LostFocus += inputBox_LostFocus;
                    inputBox.Loaded += inputBox_Loaded;
                }
    
                Button btn = this.GetTemplateChild("PART_TagButton") as Button;
                if (btn != null)
                {
                    btn.Loaded += (s, e) =>
                    {
                        Button b = s as Button;
                        var btnDelete = b.Template.FindName("PART_DeleteTagButton", b) as Button; // will only be found once button is loaded
                        if (btnDelete != null)
                        {
                            btnDelete.Click -= btnDelete_Click; // make sure the handler is applied just once
                            btnDelete.Click += btnDelete_Click;
                        }
                    };
    
                    btn.Click += (s, e) =>
                    {
                        var parent = GetParent();
                        if (parent != null)
                            parent.RaiseTagClick(this); // raise the TagClick event of the EvernoteTagControl
                    };
                }
    
                base.OnApplyTemplate();
            }
    
            /// 
            /// Handles the click on the delete glyph of the tag button.
            /// Removes the tag from the collection.
            /// 
            void btnDelete_Click(object sender, RoutedEventArgs e)
            {
    
                var item = FindUpVisualTree(sender as FrameworkElement);
                var parent = GetParent();
                if (item != null && parent != null)
                    parent.RemoveTag(item);
    
                e.Handled = true; // bubbling would raise the tag click event
            }
    
            /// 
            /// When an AutoCompleteBox is created, set the focus to the textbox.
            /// Wire PreviewKeyDown event to handle Escape/Enter keys
            /// 
            /// AutoCompleteBox.Focus() is broken: http://stackoverflow.com/questions/3572299/autocompletebox-focus-in-wpf
            void inputBox_Loaded(object sender, RoutedEventArgs e)
            {
                AutoCompleteBox acb = sender as AutoCompleteBox;
                if (acb != null)
                {
                    var tb = acb.Template.FindName("Text", acb) as TextBox;
                    if (tb != null)
                        tb.Focus();
    
                    // PreviewKeyDown, because KeyDown does not bubble up for Enter
                    acb.PreviewKeyDown += (s, e1) =>
                    {
                        var parent = GetParent();
                        if (parent != null)
                        {
                            switch (e1.Key)
                            {
                                case (Key.Enter):  // accept tag
                                    parent.Focus(); 
                                    break;
                                case (Key.Escape): // reject tag
                                    parent.Focus();
                                    parent.RemoveTag(this, true); // do not raise RemoveTag event
                                    break;
                            }
                        }
                    };
                }
            }
    
            /// 
            /// Set IsEditing to false when the AutoCompleteBox loses keyboard focus.
            /// This will change the template, displaying the tag as a button.
            /// 
            void inputBox_LostFocus(object sender, RoutedEventArgs e)
            {
                this.IsEditing = false;
                var parent = GetParent();
                if (parent != null)
                    parent.IsEditing = false;
            }
    
            private EvernoteTagControl GetParent()
            {
                return FindUpVisualTree(this);
            }
    
            /// 
            /// Walks up the visual tree to find object of type T, starting from initial object
            /// http://www.codeproject.com/Tips/75816/Walk-up-the-Visual-Tree
            /// 
            private static T FindUpVisualTree(DependencyObject initial) where T : DependencyObject
            {
                DependencyObject current = initial;
                while (current != null && current.GetType() != typeof(T))
                {
                    current = VisualTreeHelper.GetParent(current);
                }
                return current as T;
            }
        }
    }
    

    Themes/generic.xaml:

    
    
        
    
        
        
                
            
            
                
                    
                        
                    
                
            
        
    
        
        
    
    
    

提交回复
热议问题