一、功能描述
該插件的功能跟代碼文件的導航功能類似,只是下拉框里的內容是元素的某一屬性值,如圖-1所示
圖-1
當點擊下拉框的選項后,會自動定位到該內容在xml文件的位置。此功能適用于xml文件內容較多的情況。
二、選擇Editor Margin插件模板
因為該插件模板會在編輯區的底部創建一個WPF控件,如圖-2所示。
圖-2
而你可以創建一個WPF用戶控件,并將用戶控件添加到該控件里,還可以改變該控件在編輯區的位置。按照Editor Margin模板的向導建立插件項目,在項目里有三個文件:source.extension.vsixmanifest、EditorMargin1、EditorMargin1Factory,改變位置是通過EditorMargin1Factory類的MarginContainerAttribute特性實現的,該特性接收PRedefinedMarginNames靜態類的常量字段,這些常量字段定義了控件可以停靠的位置,如圖-3所示。具體的功能主要是在EditorMargin1文件里實現。
圖-3
當文檔打開的時候VS會加載MarginFactory類的CreateMargin方法執行。
三、創建WPF用戶控件
在項目里添加一個WPF用戶控件,在用戶控件里添加一個ComboBox下拉控件,當下拉框的選項改變的時候觸發定位操作。由于我們是在用戶控件里添加下拉控件,在用戶控件外部無法監控到下拉框的改變事件,所以我們需要在用戶控件里添加一個事件,在下拉框改變事件里觸發該事件,這樣就可以間接訂閱下拉框的選項改變事件。此外,還需要對外開放一個改變下拉框寬度的函數,用于編輯區大小改變的時候可以修改下拉框的寬度。具體的代碼如下所示:
/// <summary>/// MappingInfo.xaml 的交互邏輯/// </summary>public partial class MappingInfo : UserControl{ public delegate void DelegateSelectionChanged(object sender, SelectionChangedEventArgs e); public event DelegateSelectionChanged SelectionChanged; public MappingInfo() { InitializeComponent(); } public MappingInfo(IEnumerable<XElement> elements) { InitializeComponent(); List<Elements> list = new List<Elements>(); foreach (var item in elements) { if (item.Attribute("name") == null) continue; Elements model = new Elements(); model.Value = item.Attribute("name").Value; string desc = item.Attribute("title") != null ? item.Attribute("title").Value : item.Attribute("remark") == null ? "" : item.Attribute("remark").Value; string cache = item.Attribute("cache") != null ? item.Attribute("cache").Value : ""; model.Text = desc != "" ? string.Format("{0}({1})", model.Value, desc) : model.Value; if (cache != "" && cache.Equals("true", StringComparison.OrdinalIgnoreCase)) { model.Text += " √"; } list.Add(model); } cbElement.DisplayMemberPath = "Text"; cbElement.SelectedValuePath = "Value"; cbElement.ItemsSource = list; cbElement.SelectedIndex = 0; //訂閱選項改變時的事件 cbElement.SelectionChanged += cbElement_SelectionChanged; } void cbElement_SelectionChanged(object sender, SelectionChangedEventArgs e) { SelectionChanged(sender, e); } public void SetComboBoxWidth(double width) { this.cbElement.Width = width; }}class Elements{ public string Text { get; set; } public string Value { get; set; }}
在EditorMargin1類的構造函數里將自定義的wpf用戶控件添加到插件創建的控件里
//設置導航欄的相關信息this.Height = 25;this.ClipToBounds = false;this.Background = new SolidColorBrush(Colors.WhiteSmoke);this.Children.Add(mapInfo);//導航欄大小改變時改變下拉框的寬度this.SizeChanged += Navigate_SizeChanged;
四、使用戶控件自適應編輯區寬度
要實現自適應的功能只需要在XmlFileNavigation類的構造函數里訂閱SizeChanged事件,由于EditorMargin1類繼承了Canvas類,而Canvas類又從其他類繼承了SizeChanged事件,所以只要通過this.SizeChanged就可以訂閱該事件,在事件里調用創建的用戶控件對外開發的修改寬度函數即可。代碼如下所示:
/// <summary>/// 大小改變時下拉框也一起調整/// </summary>/// <param name="sender"></param>/// <param name="e"></param>void Navigate_SizeChanged(object sender, SizeChangedEventArgs e){ //調整下拉框大小 //mapinfo為添加的wpf用戶控件 mapInfo.SetComboBoxWidth(((EditorMargin1)sender).ActualWidth); }
為什么要在SizeChanged事件里設置下拉框的寬度,在EditorMargin1類的構造函數里設置就不行嗎?因為在構造函數里獲取編輯區寬度的話,第一個頁面獲取的寬度是不準確的,獲取的寬度都是800,之后打開的頁面的寬度才是正常的。有興趣的同學可以在EditorMargin1類的構造函數里添加如下的代碼,獲取文檔的寬度驗證一下
EnvDTE.DTE dte=ServiceProvider.GlobalProvider.GetService(typeof(DTE)) as DTE;double width = dte.ActiveDocument.ActiveWindow.Width;
五、根據選中的內容進行定位
由于該插件是針對xml文件的,而VS沒有提供對xml文件內容的定位方法(可能是我還不知道),所以只能通過遍歷整個文件來確定選中的內容是在文件中的行數。以下是在用戶控件的響應事件里對選中的內容進行定位的代碼:
/// <summary>/// 下拉框改變事件/// </summary>/// <param name="sender"></param>/// <param name="e"></param>void cb_SelectionChanged(object sender, SelectionChangedEventArgs e){ try { //獲取下拉框選中項 Elements model = (Elements)((ComboBox)sender).SelectedItem; //獲取DTE實例 DTE dte = ServiceProvider.GlobalProvider.GetService(typeof(DTE)) as DTE; //找出選中項在xml文件里的行數 string[] lines = File.ReadAllLines(dte.ActiveDocument.FullName); int line = 0; foreach (var item in lines) { line++; if (item != "" && item.Contains(model.Value)) { break; } } //滾動條滾動到指定行數并顯示光標 TextSelection selection = dte.ActiveDocument.Selection as TextSelection; if (selection != null) { selection.MoveToLineAndOffset(line, 3); selection.ActivePoint.TryToShow(); } } catch (Exception ex) { MessageBox.Show(ex.Message, "提示", MessageBoxButton.OK, MessageBoxImage.Error); }}
如果要開發的導航插件式針對cs文件的話可以通過下面的代碼獲取cs文件里的字段、函數、事件、屬性等的相關信息:
dte.ActiveDocument.ProjectItem.FileCodeModel
以下的代碼是針對ComboBox的美化樣式
1 <UserControl.Resources> 2 <ControlTemplate x:Key="ComboBoxToggleButton" TargetType="{x:Type ToggleButton}"> 3 <Grid> 4 <Grid.ColumnDefinitions> 5 <ColumnDefinition /> 6 <ColumnDefinition Width="15" /> 7 </Grid.ColumnDefinitions> 8 <Border 9 x:Name="Border" 10 Grid.ColumnSpan="2" 11 CornerRadius="0" 12 Background="#FCFCFC" 13 BorderBrush="#9BA7B7" 14 BorderThickness="1 1 1 1" /> 15 <Border 16 Grid.Column="0" 17 CornerRadius="0" 18 Margin="1" 19 Background="#FCFCFC" 20 BorderBrush="#9BA7B7" 21 BorderThickness="0" /> 22 <Path 23 x:Name="Arrow" 24 Grid.Column="1" 25 Fill="Black" 26 HorizontalAlignment="Center" 27 VerticalAlignment="Center" 28 Data="M 0 0 L 4 4 L 8 0 Z"/> 29 </Grid> 30 <ControlTemplate.Triggers> 31 <Trigger Property="ToggleButton.IsMouSEOver" Value="true"> 32 <Setter TargetName="Border" Property="Background" Value="#FDF4BF" /> 33 <Setter TargetName="Border" Property="BorderBrush" Value="#FFEC8B" /> 34 </Trigger> 35 <Trigger Property="ToggleButton.IsChecked" Value="true"> 36 <Setter TargetName="Border" Property="Background" Value="#FFEC8B" /> 37 </Trigger> 38 <Trigger Property="IsEnabled" Value="False"> 39 <Setter TargetName="Border" Property="Background" Value="#EEEEEE" /> 40 <Setter TargetName="Border" Property="BorderBrush" Value="#AAAAAA" /> 41 <Setter Property="Foreground" Value="#888888"/> 42 <Setter TargetName="Arrow" Property="Fill" Value="#888888" /> 43 </Trigger> 44 </ControlTemplate.Triggers>
新聞熱點
疑難解答