private async void TagList_KeyDown(object sender, KeyEventArgs e)
        {
            if (TagList.SelectedItems.Count > 0)
            {
                if (e.Key == Key.Delete)
                {
                    var list = TagList.SelectedItems.OfType <BookmarkPageDataGridItemViewModel>().ToList();
                    var tldx = TagList.DataContext as BookmarkPageDataGridViewModel;

                    var dialog = new BookmarkMessage($"{list.Count}개 북마크를 삭제할까요?");
                    if ((bool)(await DialogHost.Show(dialog, "BookmarkDialog")))
                    {
                        foreach (var ll in list)
                        {
                            List <Tuple <string, BookmarkItemModel> > rl;
                            if (ll.형 == "작가")
                            {
                                rl = BookmarkModelManager.Instance.Model.artists;
                            }
                            else if (ll.형 == "그룹")
                            {
                                rl = BookmarkModelManager.Instance.Model.groups;
                            }
                            else
                            {
                                rl = BookmarkModelManager.Instance.Model.articles;
                            }

                            for (int i = 0; i < rl.Count; i++)
                            {
                                if (rl[i].Item1 == classify_name)
                                {
                                    if (rl[i].Item2.path == ll.경로 && rl[i].Item2.stamp.ToString() == ll.추가된날짜.ToString())
                                    {
                                        rl.RemoveAt(i);
                                        BookmarkModelManager.Instance.Save();
                                        break;
                                    }
                                }
                            }
                        }

                        load();
                    }
                }
            }
        }
        private async Task classify_move(string from, string to, bool copy)
        {
            var name        = from.Split('/').Last();
            var from_parent = from.Remove(from.Length - name.Length);
            var warning     = false;
            var not_valid   = false;

            if (to == "/")
            {
                foreach (var ss in BookmarkModelManager.Instance.Model.root_classes)
                {
                    if (ss == "/" + name)
                    {
                        warning = true;
                    }
                }
            }
            else
            {
                foreach (var ss in BookmarkModelManager.Instance.Model.sub_classes)
                {
                    if (ss.Item1 == to && ss.Item2 == name)
                    {
                        warning = true;
                    }
                }
            }

            if (from.Remove(from.Length - from.Split('/').Last().Length - 1) == to)
            {
                not_valid = true;
            }

            if (warning || not_valid)
            {
                if (!not_valid)
                {
                    var dialog_error = new BookmarkMessage($"경고! {to}에 이미 {name}이 존재하기 때문에 {(copy ? "복사할" : "옮길")} 수 없어요!\r\n그래도 모든 경고를 무시하고 {(copy ? "복사를" : "이동을")} 진행할까요?\r\n하위 항목들은 자동으로 합쳐져요.");
                    if (!(bool)(await DialogHost.Show(dialog_error, "BookmarkDialog")))
                    {
                        return;
                    }
                }
                else
                {
                    var dialog_error = new BookmarkMessageOk($"부모로 바로 연결되는 상위 분류로는 {(copy ? "복사할" : "옮길")} 수 없어요!");
                    await DialogHost.Show(dialog_error, "BookmarkDialog");

                    return;
                }
            }

            if (to == "/")
            {
                to = "";
            }
            var dialog = new BookmarkMessage($"{name}을 {from_parent}에서 {to + "/"}로 {(copy ? "복사할" : "옮길")}까요?");

            if ((bool)(await DialogHost.Show(dialog, "BookmarkDialog")))
            {
                if (copy)
                {
                    copyto(from, to);
                }
                else
                {
                    moveto(from, to);
                }
                BookmarkModelManager.Instance.Save();
                init();
            }
        }
        private async void Tvi_DropAsync(object sender, DragEventArgs e)
        {
            // Drop 이벤트에선 Bubbling이 일어나기 때문에 e.Handled를 true로 조작하거나
            // lock을 사용하여 처리해줄 필요가 있음
            // 단, lock을 사용할경우 unlock처리 지연을 위한 루틴이 하나이상 있어야함
            ClassifyTree.AllowDrop = false;
            var iscopy = (e.AllowedEffects & DragDropEffects.Copy) != 0 && Keyboard.IsKeyDown(Key.LeftCtrl);

            if (e.Data.GetDataPresent("registries"))
            {
                var tt = e.Data.GetData("registries") as Tuple <string, List <BookmarkPageDataGridItemViewModel> >;

                lock (drop_lock)
                    if (drop_checker == true)
                    {
                        var tvi = sender as TreeViewItem;
                        tvi.Foreground = Brushes.Black;
                        return;
                    }
                lock (drop_lock)
                    drop_checker = true;

                var message = $"{tt.Item2.Count}개 항목을 {tt.Item1}에서 {(sender as TreeViewItem).Tag}로 {(iscopy ? "복사할" : "옮길")}까요?";
                var dialog  = new BookmarkMessage(message);
                if ((bool)(await DialogHost.Show(dialog, "BookmarkDialog")))
                {
                    if (iscopy == false)
                    {
                        // 기존항목들 삭제
                        foreach (var ll in tt.Item2)
                        {
                            List <Tuple <string, BookmarkItemModel> > rl;
                            if (ll.형 == "작가")
                            {
                                rl = BookmarkModelManager.Instance.Model.artists;
                            }
                            else if (ll.형 == "그룹")
                            {
                                rl = BookmarkModelManager.Instance.Model.groups;
                            }
                            else
                            {
                                rl = BookmarkModelManager.Instance.Model.articles;
                            }

                            for (int i = 0; i < rl.Count; i++)
                            {
                                if (rl[i].Item1 == tt.Item1 && rl[i].Item2.Equals(ll.BIM))
                                {
                                    rl.RemoveAt(i);
                                    break;
                                }
                            }
                        }
                    }

                    // 추가
                    foreach (var ll in tt.Item2)
                    {
                        List <Tuple <string, BookmarkItemModel> > rl;
                        if (ll.형 == "작가")
                        {
                            rl = BookmarkModelManager.Instance.Model.artists;
                        }
                        else if (ll.형 == "그룹")
                        {
                            rl = BookmarkModelManager.Instance.Model.groups;
                        }
                        else
                        {
                            rl = BookmarkModelManager.Instance.Model.articles;
                        }

                        rl.Add(new Tuple <string, BookmarkItemModel>((sender as TreeViewItem).Tag.ToString(), ll.BIM));
                    }

                    BookmarkModelManager.Instance.Save();
                    refresh();
                }
            }
            else if (e.Data.GetDataPresent("classify"))
            {
                var from = e.Data.GetData("classify") as string;
                var to   = (sender as TreeViewItem).Tag as string;

                lock (drop_lock)
                    if (drop_checker == true)
                    {
                        var tvi = sender as TreeViewItem;
                        tvi.Foreground = Brushes.Black;
                        return;
                    }
                lock (drop_lock)
                    drop_checker = true;

                if (from == to || (to.StartsWith(from) && !iscopy))
                {
                    var dialog = new BookmarkMessageOk($"같은 분류 또는 하위 분류로는 {(iscopy ? "복사할" : "옮길")} 수 없어요!");
                    await DialogHost.Show(dialog, "BookmarkDialog");

                    goto END;
                }

                await classify_move(from, to, iscopy);
            }
END:
            lock (drop_lock)
                drop_checker = false;
            var tvi2 = sender as TreeViewItem;

            tvi2.Foreground = Brushes.Black;
        }