Refactor MainWindow with MVVM

- Add MVVM structure(BaseViewModel)
- Create ViewModel for MainWindow
- Refactor MainWindow.xaml to support MVVM
- Move PublicAPI implementation from MainViewModel to PublicAPIInstance
This commit is contained in:
Colin Liu
2016-02-18 19:30:36 +08:00
parent a0f556b2a9
commit 8621fe2e3c
7 changed files with 1153 additions and 739 deletions

View File

@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
namespace Wox.Converters
{
class VisibilityConverter : ConvertorBase<VisibilityConverter>
{
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null || value == DependencyProperty.UnsetValue)
{
return null;
}
return bool.Parse(value.ToString()) ? Visibility.Visible : Visibility.Collapsed;
}
public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
}

View File

@@ -1,6 +1,7 @@
<Window x:Class="Wox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:wox="clr-namespace:Wox"
xmlns:vm="clr-namespace:Wox.ViewModel" xmlns:converters="clr-namespace:Wox.Converters"
Title="Wox"
Topmost="True"
Loaded="MainWindow_OnLoaded"
@@ -15,14 +16,32 @@
Style="{DynamicResource WindowStyle}"
Icon="Images\app.png"
AllowsTransparency="True"
Visibility="{Binding IsVisible,Converter={converters:VisibilityConverter}}"
>
<Window.InputBindings>
<KeyBinding Key="Esc" Command="{Binding EscCommand}"></KeyBinding>
</Window.InputBindings>
<Window.Resources>
<DataTemplate DataType="{x:Type vm:ResultPanelViewModel}">
<wox:ResultPanel></wox:ResultPanel>
</DataTemplate>
<converters:VisibilityConverter x:Key="VisibilityConverter" />
</Window.Resources>
<Border Style="{DynamicResource WindowBorderStyle}" MouseDown="Border_OnMouseDown">
<StackPanel Orientation="Vertical">
<TextBox Style="{DynamicResource QueryBoxStyle}" PreviewDragOver="TbQuery_OnPreviewDragOver" AllowDrop="True"
x:Name="tbQuery" PreviewKeyDown="TbQuery_OnPreviewKeyDown" TextChanged="TbQuery_OnTextChanged" />
<Line Style="{DynamicResource PendingLineStyle}" x:Name="progressBar" Y1="0" Y2="0" X2="100" Height="2" StrokeThickness="1"></Line>
<wox:ResultPanel x:Name="pnlResult" />
<wox:ResultPanel x:Name="pnlContextMenu" Visibility="Collapsed" />
<TextBox Style="{DynamicResource QueryBoxStyle}" Text="{Binding QueryText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
PreviewDragOver="TbQuery_OnPreviewDragOver" AllowDrop="True"
x:Name="tbQuery" />
<Line Style="{DynamicResource PendingLineStyle}" x:Name="progressBar" Y1="0" Y2="0" X2="100" Height="2" StrokeThickness="1"
Visibility="{Binding IsProgressBarVisible,Converter={StaticResource VisibilityConverter}}">
<Line.ToolTip>
<ToolTip IsOpen="{Binding IsProgressBarTooltipVisible}"></ToolTip>
</Line.ToolTip>
</Line>
<ContentControl Content="{Binding SearchResultPanel}" Visibility="{Binding IsSearchResultPanelVisible,Converter={StaticResource VisibilityConverter}}">
</ContentControl>
<ContentControl Content="{Binding ActionPanel}" Visibility="{Binding IsActionPanelVisible,Converter={StaticResource VisibilityConverter}}">
</ContentControl>
</StackPanel>
</Border>
</Window>

File diff suppressed because it is too large Load Diff

309
Wox/PublicAPIInstance.cs Normal file
View File

@@ -0,0 +1,309 @@
using NHotkey;
using NHotkey.Wpf;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using Wox.Core.Plugin;
using Wox.Core.Resource;
using Wox.Core.UserSettings;
using Wox.Helper;
using Wox.Infrastructure.Hotkey;
using Wox.Plugin;
using Wox.ViewModel;
namespace Wox
{
public class PublicAPIInstance : IPublicAPI
{
#region Constructor
public PublicAPIInstance(MainViewModel mainVM)
{
this.MainVM = mainVM;
ThreadPool.SetMaxThreads(30, 10);
ThreadPool.SetMinThreads(10, 5);
GlobalHotkey.Instance.hookedKeyboardCallback += KListener_hookedKeyboardCallback;
WebRequest.RegisterPrefix("data", new DataWebRequestFactory());
SetHotkey(UserSettingStorage.Instance.Hotkey, OnHotkey);
SetCustomPluginHotkey();
}
#endregion
#region Properties
private MainViewModel MainVM
{
get;
set;
}
#endregion
#region Public API
public void ChangeQuery(string query, bool requery = false)
{
this.MainVM.QueryText = query;
//Application.Current.Dispatcher.Invoke(() =>
//{
// tbQuery.CaretIndex = this.MainVM.QueryText.Length;
// if (requery)
// {
// TbQuery_OnTextChanged(null, null);
// }
//});
}
public void ChangeQueryText(string query, bool selectAll = false)
{
this.MainVM.QueryText = query;
//Application.Current.Dispatcher.Invoke(() =>
//{
// tbQuery.CaretIndex = tbQuery.Text.Length;
// if (selectAll)
// {
// tbQuery.SelectAll();
// }
//});
}
public void CloseApp()
{
//notifyIcon.Visible = false;
Application.Current.Shutdown();
}
public void RestarApp()
{
ProcessStartInfo info = new ProcessStartInfo
{
FileName = Application.ResourceAssembly.Location,
Arguments = SingleInstance<App>.Restart
};
Process.Start(info);
}
public void HideApp()
{
HideWox();
}
public void ShowApp()
{
ShowWox();
}
public void ShowMsg(string title, string subTitle, string iconPath)
{
Application.Current.Dispatcher.Invoke(() =>
{
var m = new Msg { Owner = Application.Current.MainWindow };
m.Show(title, subTitle, iconPath);
});
}
public void OpenSettingDialog(string tabName = "general")
{
Application.Current.Dispatcher.Invoke(() =>
{
SettingWindow sw = SingletonWindowOpener.Open<SettingWindow>(this);
sw.SwitchTo(tabName);
});
}
public void StartLoadingBar()
{
this.MainVM.IsProgressBarVisible = true;
}
public void StopLoadingBar()
{
this.MainVM.IsProgressBarVisible = false;
}
public void InstallPlugin(string path)
{
Application.Current.Dispatcher.Invoke(() => PluginManager.InstallPlugin(path));
}
public void ReloadPlugins()
{
Application.Current.Dispatcher.Invoke(() => PluginManager.Init(this));
}
public string GetTranslation(string key)
{
return InternationalizationManager.Instance.GetTranslation(key);
}
public List<PluginPair> GetAllPlugins()
{
return PluginManager.AllPlugins.ToList();
}
public event WoxKeyDownEventHandler BackKeyDownEvent;
public event WoxGlobalKeyboardEventHandler GlobalKeyboardEvent;
public event ResultItemDropEventHandler ResultItemDropEvent;
public void PushResults(Query query, PluginMetadata plugin, List<Result> results)
{
results.ForEach(o =>
{
o.PluginDirectory = plugin.PluginDirectory;
o.PluginID = plugin.ID;
o.OriginQuery = query;
});
this.MainVM.UpdateResultView(results, plugin, query);
}
public void ShowContextMenu(PluginMetadata plugin, List<Result> results)
{
if (results != null && results.Count > 0)
{
results.ForEach(o =>
{
o.PluginDirectory = plugin.PluginDirectory;
o.PluginID = plugin.ID;
});
this.MainVM.ActionPanel.Clear();
//TODO:Show Action Panel accordingly
//pnlContextMenu.Clear();
//pnlContextMenu.AddResults(results, plugin.ID);
//pnlContextMenu.Visibility = Visibility.Visible;
//pnlResult.Visibility = Visibility.Collapsed;
}
}
#endregion
#region Private Methods
private bool KListener_hookedKeyboardCallback(KeyEvent keyevent, int vkcode, SpecialKeyState state)
{
if (GlobalKeyboardEvent != null)
{
return GlobalKeyboardEvent((int)keyevent, vkcode, state);
}
return true;
}
private void HideWox()
{
UserSettingStorage.Instance.WindowLeft = this.MainVM.Left;
UserSettingStorage.Instance.WindowTop = this.MainVM.Top;
this.MainVM.IsVisible = false;
}
private void ShowWox(bool selectAll = true)
{
UserSettingStorage.Instance.IncreaseActivateTimes();
this.MainVM.IsVisible = true;
//TODO:Adjust window properties
//Left = GetWindowsLeft();
//Top = GetWindowsTop();
//Show();
//Activate();
//Focus();
//tbQuery.Focus();
//ResetQueryHistoryIndex();
//if (selectAll) tbQuery.SelectAll();
}
public void SetHotkey(string hotkeyStr, EventHandler<HotkeyEventArgs> action)
{
var hotkey = new HotkeyModel(hotkeyStr);
SetHotkey(hotkey, action);
}
public void SetHotkey(HotkeyModel hotkey, EventHandler<HotkeyEventArgs> action)
{
string hotkeyStr = hotkey.ToString();
try
{
HotkeyManager.Current.AddOrReplace(hotkeyStr, hotkey.CharKey, hotkey.ModifierKeys, action);
}
catch (Exception)
{
string errorMsg = string.Format(InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"), hotkeyStr);
MessageBox.Show(errorMsg);
}
}
public void RemoveHotkey(string hotkeyStr)
{
if (!string.IsNullOrEmpty(hotkeyStr))
{
HotkeyManager.Current.Remove(hotkeyStr);
}
}
/// <summary>
/// Checks if Wox should ignore any hotkeys
/// </summary>
/// <returns></returns>
private bool ShouldIgnoreHotkeys()
{
//double if to omit calling win32 function
if (UserSettingStorage.Instance.IgnoreHotkeysOnFullscreen)
if (WindowIntelopHelper.IsWindowFullscreen())
return true;
return false;
}
private void SetCustomPluginHotkey()
{
if (UserSettingStorage.Instance.CustomPluginHotkeys == null) return;
foreach (CustomPluginHotkey hotkey in UserSettingStorage.Instance.CustomPluginHotkeys)
{
CustomPluginHotkey hotkey1 = hotkey;
SetHotkey(hotkey.Hotkey, delegate
{
if (ShouldIgnoreHotkeys()) return;
ShowApp();
ChangeQuery(hotkey1.ActionKeyword, true);
});
}
}
private void OnHotkey(object sender, HotkeyEventArgs e)
{
if (ShouldIgnoreHotkeys()) return;
ToggleWox();
e.Handled = true;
}
private void ToggleWox()
{
if (!MainVM.IsVisible)
{
ShowWox();
}
else
{
HideWox();
}
}
#endregion
}
}

View File

@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace Wox.ViewModel
{
public class BaseViewModel : INotifyPropertyChanged
{
protected virtual void OnPropertyChanged(string propertyName)
{
if (null != this.PropertyChanged)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class RelayCommand : ICommand
{
private Action _action;
public RelayCommand(Action action)
{
this._action = action;
}
public virtual bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public virtual void Execute(object parameter)
{
if (null != this._action)
{
this._action();
}
}
}
}

View File

@@ -0,0 +1,449 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using Wox.Core.Plugin;
using Wox.Core.Resource;
using Wox.Infrastructure;
using Wox.Infrastructure.Hotkey;
using Wox.Plugin;
using Wox.Storage;
namespace Wox.ViewModel
{
public class MainViewModel : BaseViewModel
{
#region Private Fields
private ResultPanelViewModel _searchResultPanel;
private ResultPanelViewModel _actionPanel;
private string _queryText;
private bool _isVisible;
private bool _isSearchResultPanelVisible;
private bool _isActionPanelVisible;
private bool _isProgressBarVisible;
private bool _isProgressBarTooltipVisible;
private double _left;
private double _top;
private bool _queryHasReturn;
private Query _lastQuery = new Query();
private bool _ignoreTextChange;
private List<Result> CurrentContextMenus = new List<Result>();
private string _textBeforeEnterContextMenuMode;
#endregion
#region Constructor
public MainViewModel()
{
this.InitializeResultPanel();
this.InitializeActionPanel();
this.InitializeKeyCommands();
this._queryHasReturn = false;
}
#endregion
#region ViewModel Properties
public ResultPanelViewModel SearchResultPanel
{
get
{
return this._searchResultPanel;
}
}
public ResultPanelViewModel ActionPanel
{
get
{
return this._actionPanel;
}
}
public string QueryText
{
get
{
return this._queryText;
}
set
{
this._queryText = value;
OnPropertyChanged("QueryText");
this.HandleQueryTextUpdated();
}
}
public bool IsVisible
{
get
{
return this._isVisible;
}
set
{
this._isVisible = value;
OnPropertyChanged("IsVisible");
if (!value && this.IsActionPanelVisible)
{
this.BackToSearchMode();
}
}
}
public bool IsSearchResultPanelVisible
{
get
{
return this._isSearchResultPanelVisible;
}
set
{
this._isSearchResultPanelVisible = value;
OnPropertyChanged("IsSearchResultPanelVisible");
}
}
public bool IsActionPanelVisible
{
get
{
return this._isActionPanelVisible;
}
set
{
this._isActionPanelVisible = value;
OnPropertyChanged("IsActionPanelVisible");
}
}
public bool IsProgressBarVisible
{
get
{
return this._isProgressBarVisible;
}
set
{
this._isProgressBarVisible = value;
OnPropertyChanged("IsProgressBarVisible");
}
}
public bool IsProgressBarTooltipVisible
{
get
{
return this._isProgressBarTooltipVisible;
}
set
{
this._isProgressBarTooltipVisible = value;
OnPropertyChanged("IsProgressBarTooltipVisible");
}
}
public double Left
{
get
{
return this._left;
}
set
{
this._left = value;
OnPropertyChanged("Left");
}
}
public double Top
{
get
{
return this._top;
}
set
{
this._top = value;
OnPropertyChanged("Top");
}
}
public ICommand EscCommand
{
get;
set;
}
#endregion
#region Private Methods
private void InitializeKeyCommands()
{
this.EscCommand = new RelayCommand(() => {
if (this.IsActionPanelVisible)
{
this.BackToSearchMode();
}
else
{
this.IsVisible = false;
}
});
}
private void InitializeResultPanel()
{
this._searchResultPanel = new ResultPanelViewModel();
this.IsSearchResultPanelVisible = false;
this._searchResultPanel.ResultOpenedInPanel += (o, e) =>
{
if (e.HideWindow)
{
this.IsVisible = false;
}
UserSelectedRecordStorage.Instance.Add(e.Result.RawResult);
QueryHistoryStorage.Instance.Add(this.QueryText);
};
this._searchResultPanel.ResultActionPanelOpenedInPanel += (o, e) =>
{
this.ShowActionPanel(e.Result.RawResult);
};
}
private void ShowActionPanel(Result result)
{
if (result == null) return;
List<Result> results = PluginManager.GetContextMenusForPlugin(result);
results.ForEach(o =>
{
o.PluginDirectory = PluginManager.GetPluginForId(result.PluginID).Metadata.PluginDirectory;
o.PluginID = result.PluginID;
o.OriginQuery = result.OriginQuery;
});
results.Add(GetTopMostContextMenu(result));
_textBeforeEnterContextMenuMode = this.QueryText;
this._actionPanel.Clear();
this._actionPanel.AddResults(results, result.PluginID);
CurrentContextMenus = results;
this.IsActionPanelVisible = true;
this.IsSearchResultPanelVisible = false;
this.QueryText = "";
}
private Result GetTopMostContextMenu(Result result)
{
if (TopMostRecordStorage.Instance.IsTopMost(result))
{
return new Result(InternationalizationManager.Instance.GetTranslation("cancelTopMostInThisQuery"), "Images\\down.png")
{
PluginDirectory = WoxDirectroy.Executable,
Action = _ =>
{
TopMostRecordStorage.Instance.Remove(result);
//TODO:Modify the way showing this message
//ShowMsg("Succeed", "", "");
return false;
}
};
}
else
{
return new Result(InternationalizationManager.Instance.GetTranslation("setAsTopMostInThisQuery"), "Images\\up.png")
{
PluginDirectory = WoxDirectroy.Executable,
Action = _ =>
{
TopMostRecordStorage.Instance.AddOrUpdate(result);
//TODO:Modify the way showing this message
//ShowMsg("Succeed", "", "");
return false;
}
};
}
}
private void InitializeActionPanel()
{
this._actionPanel = new ResultPanelViewModel();
this.IsActionPanelVisible = false;
this._actionPanel.ResultOpenedInPanel += (o, e) =>
{
if (e.HideWindow)
{
this.IsVisible = false;
}
};
}
private void HandleQueryTextUpdated()
{
if (_ignoreTextChange) { _ignoreTextChange = false; return; }
this.IsProgressBarTooltipVisible = false;
if (this.IsActionPanelVisible)
{
QueryActionPanel();
}
else
{
string query = this.QueryText.Trim();
if (!string.IsNullOrEmpty(query))
{
Query(query);
//reset query history index after user start new query
ResetQueryHistoryIndex();
}
else
{
this._searchResultPanel.Clear();
}
}
}
private void QueryActionPanel()
{
var contextMenuId = "Context Menu Id";
this._actionPanel.Clear();
var query = this.QueryText.ToLower();
if (string.IsNullOrEmpty(query))
{
this._actionPanel.AddResults(CurrentContextMenus, contextMenuId);
}
else
{
List<Result> filterResults = new List<Result>();
foreach (Result contextMenu in CurrentContextMenus)
{
if (StringMatcher.IsMatch(contextMenu.Title, query)
|| StringMatcher.IsMatch(contextMenu.SubTitle, query))
{
filterResults.Add(contextMenu);
}
}
this._actionPanel.AddResults(filterResults, contextMenuId);
}
}
private void Query(string text)
{
_queryHasReturn = false;
var query = PluginManager.QueryInit(text);
if (query != null)
{
// handle the exclusiveness of plugin using action keyword
string lastKeyword = _lastQuery.ActionKeyword;
string keyword = query.ActionKeyword;
if (string.IsNullOrEmpty(lastKeyword))
{
if (!string.IsNullOrEmpty(keyword))
{
this._searchResultPanel.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata);
}
}
else
{
if (string.IsNullOrEmpty(keyword))
{
this._searchResultPanel.RemoveResultsFor(PluginManager.NonGlobalPlugins[lastKeyword].Metadata);
}
else if (lastKeyword != keyword)
{
this._searchResultPanel.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata);
}
}
_lastQuery = query;
Action action = new Action(async () =>
{
await Task.Delay(150);
if (!string.IsNullOrEmpty(query.RawQuery) && query.RawQuery == _lastQuery.RawQuery && !_queryHasReturn)
{
this.IsProgressBarTooltipVisible = true;
}
});
action.Invoke();
//Application.Current.Dispatcher.InvokeAsync(async () =>
//{
// await Task.Delay(150);
// if (!string.IsNullOrEmpty(query.RawQuery) && query.RawQuery == _lastQuery.RawQuery && !_queryHasReturn)
// {
// StartProgress();
// }
//});
PluginManager.QueryForAllPlugins(query);
}
this.IsProgressBarTooltipVisible = false;
}
private void ResetQueryHistoryIndex()
{
this._searchResultPanel.RemoveResultsFor(QueryHistoryStorage.MetaData);
QueryHistoryStorage.Instance.Reset();
}
private void UpdateResultViewInternal(List<Result> list, PluginMetadata metadata)
{
Stopwatch.Normal($"UI update cost for {metadata.Name}",
() => { this._searchResultPanel.AddResults(list, metadata.ID); });
}
private void BackToSearchMode()
{
this.QueryText = _textBeforeEnterContextMenuMode;
this.IsActionPanelVisible = false;
this.IsSearchResultPanelVisible = true;
}
#endregion
#region Public Methods
public void UpdateResultView(List<Result> list, PluginMetadata metadata, Query originQuery)
{
_queryHasReturn = true;
this.IsProgressBarTooltipVisible = false;
list.ForEach(o =>
{
o.Score += UserSelectedRecordStorage.Instance.GetSelectedCount(o) * 5;
});
if (originQuery.RawQuery == _lastQuery.RawQuery)
{
Application.Current.Dispatcher.Invoke(() => {
UpdateResultViewInternal(list, metadata);
});
}
if(list.Count > 0)
{
this.IsSearchResultPanelVisible = true;
}
}
#endregion
}
}