I've been struggling with the Samsung Galaxy numeric keyboard decimal separator nightmare.
I have an Entry on which I must only accept numeric values (including decimals), so in my xaml code I wrote <Entry Keyboard="Numeric"/> and it worked just fine on the VS19 android simulator. But when I run the app on my physical device (Samsung Galaxy S10+) the comma key is disabled and the .- key doesn't work (it types nothing).
I made some digging online and found a few solutions, but none worked for me.
SOLUTION 1
Forcing the app culture to pt-BR (because my phone system language is brazilian portuguese) by adding this code to App() (in App.xaml.cs):
private void SetCultureToPTBR()
{
CultureInfo br = new CultureInfo("pt-BR");
CultureInfo.DefaultThreadCurrentCulture = br;
}
But there were no changes.
SOLUTION 2
Setting input type on a custom renderer for entries:
[assembly: ExportRenderer(typeof(Entry), typeof(CustomEntryRenderer))]
namespace AppCoperNitro.Droid.CustomRenderers
{
public class CustomEntryRenderer : EntryRenderer
{
public CustomEntryRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control == null || e.NewElement == null)
return;
this.Control.KeyListener = DigitsKeyListener.GetInstance(true, true);
this.Control.InputType = Android.Text.InputTypes.ClassNumber | Android.Text.InputTypes.NumberFlagDecimal;
}
}
}
This method changed the .- key to a comma key, and it kinda works (it types a .), but the decimal place is ignored (if I type 2.3 the app receives it as 23).
SOLUTION 3
This solution, but the result was the same as solution 2.
I also tried combining these solutions and their variances, but nothing works the way it should. I don't even care if the entry shows a comma or a dot, I just need the decimal number to be received correctly.
We can change the ways. According to your SOLUTION 2, the entry can input the ., so you can do something in the Entry's UnFocused event. Such as: // Use Gabic's code
private void Entry_Unfocused(object sender, FocusEventArgs e)
{
Entry entry = sender as Entry;
if (entry.Text.Length > 0 && Android.OS.Build.Manufacturer.Equals("Samsung", StringComparison.CurrentCultureIgnoreCase))
{
string a = entry.Text.Replace('.', ',');
entry.Text = a;
}
}
And then add the event into the xaml:
<Entry Keyboard="Numeric" Unfocused="Entry_Unfocused"/>
Related
currently I am facing the following problem.
In my application (which is a plugin for AutoCAD/BricksCAD), i am using a DataGrid with a custom Column, that ensures the input to be in a float-format.
The code for the column looks something like that:
public class DataGridFloatColumn : DataGridTextColumn
{
protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
{
TextBox edit = editingElement as TextBox;
edit.PreviewTextInput += OnPreviewTextInput;
return base.PrepareCellForEdit(editingElement, editingEventArgs);
}
void OnPreviewTextInput(object sender, TextCompositionEventArgs e)
{
string txt = ((TextBox)sender).Text + e.Text;
float _;
e.Handled = !float.TryParse(txt, out _);
}
}
This part works as expected. I am only allowed to input float numbers, separated by a comma. The problem however is, that the DataGrid expects a dot as decimal separator.
Due to this, initial values are represented with a dot, and If I type something with a comma, it simply gets removed when hitting enter... for example 123,4 --> 1234
What I have already tried:
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("de-DE");
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("de-DE");
But when printing Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator, i am getting a comma...
Any suggestions?
Thank you ind advance!
I dont know if it covers your issue, but if I want culture-specific number/datetime formatting in XAML, I put this "hack" into the constructor of my "App.xaml.cs"
public App()
{
FrameworkElement.LanguageProperty.OverrideMetadata(
typeof(FrameworkElement),
new FrameworkPropertyMetadata(XmlLanguage.GetLanguage("de-DE")));
}
Thread.CurrentThread.CurrentCulture = myCulture somehow does not do the trick.
I have an entry in xaml:
<Entry Grid.Column="1" Grid.RowSpan="1"
FontAttributes="None" FontSize="Large"
IsPassword="False" Keyboard="Numeric"
HorizontalTextAlignment="Center" VerticalOptions="StartAndExpand"
Text="{Binding MyNumber}"
TextColor="{Binding PropertyTextColor, Converter={StaticResource StringToColorConverter}}"
Completed="Entry_Completed"/>
When I have US culture-info everything works properly, but when culture info is changed I cannot enter a comma or dot separator for decimal input. For example, when I type 5 and enter ',' and type 5 then the value converts to 55. I tried to change the culture-info for the current thread, but it does not help.
private decimal _myNumber;
public decimal MyNumber
{
get
{
return myNumber;
}
set
{
System.Threading.Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en-US");
if (_myNumber != value)
{
if (IsDivisible)
//_myNumber = Convert.ToDecimal(Math.Round(value, 2).ToString(), new CultureInfo("pl-PL"));
_ = decimal.TryParse(value.ToString(), NumberStyles.Any, new CultureInfo("pl-PL"), out _count);
else
_ = decimal.TryParse(value.ToString(), NumberStyles.Any, new CultureInfo("pl-PL"), out _count);
// _myNumber = Convert.ToDecimal(Math.Round(value, 0).ToString(), new CultureInfo("pl-PL"));
OnPropertyChanged(nameof(Count));
}
}
}
Is there any way to work around this issue? Because as I understood this problem is related to the Samsung configuration or settings and it's a quite common problem in Xamarin.
In my case i ended up needing to force the dot as the decimal separator for every culture. And in order to get the dot printed i also needed to create a custom renderer for the Entry in order to re-set the InputTypes flags.
If this sounds like a way you might want to follow, keep on reading.
What follows is a sample-minimal app to demonstrate how to force and use the dot as a decimal separator in Android for Xamarin.Forms
Force the dot as the decimal separator
First we have to tell the App that no matter what culture is used, the dot has to be taken as the decimal separator:
public App()
{
CultureInfo customCulture = (CultureInfo)System.Threading.Thread.CurrentThread.CurrentCulture.Clone();
customCulture.NumberFormat.NumberDecimalSeparator = ".";
System.Threading.Thread.CurrentThread.CurrentCulture = customCulture;
InitializeComponent();
MainPage = new MainPage();
}
Re-set InputType
Create a CustomEntry on your Xamarin.Forms shared project:
using Xamarin.Forms;
namespace EntryDecimal
{
public class MyCustomEntry : Entry
{
}
}
And then add the custom renderer on Android. To do this simply add a new class to the Android project called AndroidCustomEntryRenderer.cs with the following content:
using System.ComponentModel;
using Android.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Android.Text.Method;
using Android.Content;
using EntryDecimal.Droid;
using EntryDecimal;
[assembly: ExportRenderer(typeof(MyCustomEntry), typeof(AndroidCustomEntryRenderer))]
namespace EntryDecimal.Droid
{
public class AndroidCustomEntryRenderer : EntryRenderer
{
private MyCustomEntry element;
private EditText native;
public AndroidCustomEntryRenderer(Context context) : base(context)
{ }
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
//Java.Text.DecimalFormatSymbols.Instance.DecimalSeparator = '.';
element = (MyCustomEntry)Element ?? null;
native = Control as EditText;
UpdateKeyboard();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (Control == null) return;
else if (e.PropertyName == InputView.KeyboardProperty.PropertyName)
UpdateKeyboard();
}
private void UpdateKeyboard()
{
//Implementation of the numeric keyboard (we simply add the NumberFlagSigned)
native = Control as EditText;
var defaultNumericKeyboard = Android.Text.InputTypes.ClassNumber | Android.Text.InputTypes.NumberFlagDecimal;
var correnctNumericKeyboard = Android.Text.InputTypes.ClassNumber | Android.Text.InputTypes.NumberFlagSigned | Android.Text.InputTypes.NumberFlagDecimal;
if (native.InputType == defaultNumericKeyboard)
{
native.InputType =
Android.Text.InputTypes.ClassNumber |
Android.Text.InputTypes.NumberFlagSigned |
Android.Text.InputTypes.NumberFlagDecimal;
native.KeyListener = DigitsKeyListener.GetInstance(string.Format("7890.-"));
}
else if (native.InputType == correnctNumericKeyboard)
{
// Even though in the next line the InputType is set to the same it is already set, this seems to
// fix the problem with the decimal separator: Namely, a local other than english is set, the point
// does not have any effect in the numeric keyboard. Re-setting the InputType seems to fix this.
native.InputType =
Android.Text.InputTypes.ClassNumber |
Android.Text.InputTypes.NumberFlagSigned |
Android.Text.InputTypes.NumberFlagDecimal;
}
}
}
}
And finally consume this:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:EntryDecimal"
x:Class="EntryDecimal.MainPage">
<StackLayout>
<local:MyCustomEntry x:Name="MyEntry" Keyboard="Numeric"
WidthRequest="150"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"
Completed="MyCustomEntry_Completed"/>
</StackLayout>
</ContentPage>
This minimal blank app is working correctly on my side, accepting dot as decimal separator for any culture (like de [german] where decimal separator is ",").
Hope this works!
Since I have updated to Xcode 12 and VS (2019 8.7.8 build 4) on my mac and updated Xamarin to the latest version (Xamarin.iOS 14.0.0.0), I find I am unable to use UIDatePicker.
DatePicker
Looking at the documentation, https://developer.apple.com/documentation/uikit/uidatepicker I need to set Style and maybe preferredDatePickerStyle but neither are these are properties I can set in the (Xamarin) code.
Has anyone found a way to get past this to enable the date to be selected?
It will work once we set the preferredDatePickerStyle and sizeToFit, Sample code is given below.
_datePicker = new UIDatePicker(new CGRect(0, 30, 0, 0));
_datePicker.AutoresizingMask = UIViewAutoresizing.FlexibleRightMargin;
_datePicker.Frame = new CGRect(_datePicker.Frame.Location, new CGSize(300, _datePicker.Frame.Size.Height));
_datePicker.Mode = _datePickerMode;
_datePicker.Date = (NSDate)_defaultDate;
_datePicker.PreferredDatePickerStyle = UIDatePickerStyle.Wheels; //Add this in ios14
_datePicker.SizeToFit(); //Add this in ios14
I have resolved it with this:
_datePicker.SetValueForKey(new NSNumber(1), new NSString("preferredDatePickerStyle"));
It is now showing the correct selector
Date Selector
I'm basically putting together all the details of the other answers into the corresponding implementation in public class CustomDatePickerRenderer : DatePickerRenderer for Xamarin Forms in iOS (omitted my implementation of CreateNativeControl, OnElementPropertyChanged, and custom methods for brevity):
protected override void OnElementChanged(ElementChangedEventArgs<DatePicker> e)
{
const int iosVersionMajorForDatePickerStyleFeature = 13;
const int iosVersionMinorForDatePickerStyleFeature = 4;
base.OnElementChanged(e);
if (e.OldElement != null || Element == null)
{
return;
}
//Switch back to the Wheels style for the datePicker (in iOS 14 the default of Automatic uses the Compact style)
if (UIDevice.CurrentDevice.CheckSystemVersion(iosVersionMajorForDatePickerStyleFeature, iosVersionMinorForDatePickerStyleFeature)
&& Control?.InputView is UIDatePicker datePicker && datePicker.PreferredDatePickerStyle != UIDatePickerStyle.Wheels)
{
datePicker.PreferredDatePickerStyle = UIDatePickerStyle.Wheels;
datePicker.SizeToFit();
}
//Do whatever other customization is desired
//UpdateBorderColor();
//UpdateBorderWidth();
//UpdateCornerRadius();
//UpdatePadding();
}
I also encountered this interesting problem recently. When we used Xcode 12 to package it, everything was normal, but when switching to Xcode 12 and above for packaging date selection and display, it would appear abnormal.
Because iOS 13.4 UIDatePicker has new features
#property (nonatomic, readwrite, assign) UIDatePickerStyle preferredDatePickerStyle API_AVAILABLE(ios(13.4)) API_UNAVAILABLE(tvos, watchos);
If you want to keep the original style, you can add this code
_datePick = [[UIDatePicker alloc] init];
if (#available(iOS 13.4, *)) {
_datePick.preferredDatePickerStyle = UIDatePickerStyleWheels;
}
The controls on my WebPart are not being created because the condition:
if (this.dpsvisWebPart != null && this.dpsvisWebPart.CustomTitleProp != null)
...fails due to CustomTitleProp being null. Yet it is declared in the WebPart's constructor and should not be null.
Here is the code:
namespace DirectPaymentSectionsWebPart.DPSVisualWebPart
{
[ToolboxItemAttribute(false)]
public class DPSVisualWebPart : WebPart
{
// Text
[WebBrowsable(true),
Category("Financial Affairs Forms"),
Personalizable(PersonalizationScope.Shared),
WebDisplayName("WebPart Title Text")]
public string CustomTitleProp { get; set; }
// Checkboxes
[WebBrowsable(true),
Category("Financial Affairs Forms"),
Personalizable(PersonalizationScope.Shared),
WebDisplayName("Generate Section1")]
public bool CheckboxGenSection1 { get; set; }
. . .
// Visual Studio might automatically update this path when you change the Visual Web Part project item.
private const string _ascxPath = #"~/_CONTROLTEMPLATES/DirectPaymentSectionsWebPart/DPSVisualWebPart/DPSVisualWebPartUserControl.ascx";
. . .
protected override void CreateChildControls()
{
//base.CreateChildControls(); // added this 5/28/2015 <= commented out, because it is being called over and over again
DPSVisualWebPartUserControl control = Page.LoadControl(_ascxPath) as DPSVisualWebPartUserControl;
//DPSVisualWebPartUserControl control = Page.LoadControl(siteUrl) as DPSVisualWebPartUserControl;
if (control != null)
{
control.dpsvisWebPart = this;
}
Controls.Add(control);
}
} // class DPSVisualWebPart
So why might it be that CustomTitleProp is null at the previously noted juncture? Here that code is with more context:
public partial class DPSVisualWebPartUserControl : UserControl
{
public DPSVisualWebPart dpsvisWebPart { get; set; }
System.Web.UI.ScriptManager scriptMgr;
. . .
protected void Page_Load(object sender, EventArgs e)
{
base.OnPreRender(e);
if (System.Web.UI.ScriptManager.GetCurrent(this.Page) == null)
{
scriptMgr = new System.Web.UI.ScriptManager();
scriptMgr.EnablePartialRendering = true;
this.Controls.AddAt(0, scriptMgr);
}
if (this.dpsvisWebPart != null && this.dpsvisWebPart.CustomTitleProp != null)
{
lbl_Title.Text = String.Format("<h1>{0}</h1>", this.dpsvisWebPart.CustomTitleProp.ToString());
. . .
UPDATE
Under the heading of "Adventures in Folder Diffing" comes this:
The legacy project contains a file named FileListAbsolute.txt with this content:
C:\Projects\DirectPaymentSectionsWebPart\DirectPaymentSectionsWebPart\bin\Debug\DirectPaymentSectionsWebPart.dll
C:\Projects\DirectPaymentSectionsWebPart\DirectPaymentSectionsWebPart\bin\Debug\DirectPaymentSectionsWebPart.pdb
C:\Projects\DirectPaymentSectionsWebPart\DirectPaymentSectionsWebPart\bin\Debug\Microsoft.Sharepoint.Sandbox.dll
C:\Projects\DirectPaymentSectionsWebPart\DirectPaymentSectionsWebPart\obj\Debug\ResolveAssemblyReference.cache
C:\Projects\DirectPaymentSectionsWebPart\DirectPaymentSectionsWebPart\obj\Debug\DirectPaymentSectionsWebPart.dll
C:\Projects\DirectPaymentSectionsWebPart\DirectPaymentSectionsWebPart\obj\Debug\DirectPaymentSectionsWebPart.pdb
The new project contains a file of the same name with this content:
C:\Projects\DirectPaymentSectionsWebPart\DirectPaymentSectionsWebPart\bin\Debug\DirectPaymentSectionsWebPart.dll
C:\Projects\DirectPaymentSectionsWebPart\DirectPaymentSectionsWebPart\bin\Debug\DirectPaymentSectionsWebPart.pdb
C:\Projects\DirectPaymentSectionsWebPart\DirectPaymentSectionsWebPart\bin\Debug\Microsoft.Sharepoint.Sandbox.dll
C:\Projects\DirectPaymentSectionsWebPart\DirectPaymentSectionsWebPart\obj\Debug\ResolveAssemblyReference.cache
C:\Projects\DirectPaymentSectionsWebPart\DirectPaymentSectionsWebPart\obj\Debug\DirectPaymentSectionsWebPart.dll
C:\Projects\DirectPaymentSectionsWebPart\DirectPaymentSectionsWebPart\obj\Debug\DirectPaymentSectionsWebPart.pdb
C:\Projects\DirectPaymentWebForm\DirectPaymentSectionsWebPart\obj\Debug\ResolveAssemblyReference.cache
C:\Projects\DirectPaymentWebForm\DirectPaymentSectionsWebPart\obj\Debug\DirectPaymentSectionsWebPart.dll
C:\Projects\DirectPaymentWebForm\DirectPaymentSectionsWebPart\bin\Debug\DirectPaymentSectionsWebPart.dll
C:\Projects\DirectPaymentWebForm\DirectPaymentSectionsWebPart\bin\Debug\DirectPaymentSectionsWebPart.pdb
C:\Projects\DirectPaymentWebForm\DirectPaymentSectionsWebPart\bin\Debug\Microsoft.Sharepoint.Sandbox.dll
C:\Projects\DirectPaymentWebForm\DirectPaymentSectionsWebPart\obj\Debug\DirectPaymentSectionsWebPart.pdb
So I wonder: should I remove the first six entries from the new project's file, which reference the legacy project? Or will doing so cause Pearl Harbor III to erupt?
UPDATE 2
I tried this:
...and it turns out that there already is a ScriptManager by this point (Page_Load()) event, and that it already has EnablePartialRendering set, and so this code is moot. The question still is, though, why in Hec Ramsay is CustomTitle null here:
if (this.dpsvisWebPart != null && this.dpsvisWebPart.CustomTitleProp != null)
...which comes right after the (about-to-be-removed-because-its-moot) code in the Page_Load() event?
UPDATE 3
Alright (or is it "all right"?) - which is, at any rate, a funny thing to say when obviously not everything is all right, but then again, it's Friday, so it's okay...anyway:
I wondered: "maybe 'CustomTitleProp' is null because the Web Editor (after all, that's where CustomTitleProp lives) is not being created/rendered. And sure enough, when I went to "Edit Web Part" the Web Part Editor wouldn't even display, and I again got blasted with the red-hot, reeking-breath Sharepoint mother-in-law thus:
"Web Part Error: A Web Part or Web Form Control on this Page cannot be displayed or imported. The type DirectPaymentSectionsWebPart.DPSVisualWebPart.DPSVisualWebPart, DirectPaymentSectionsWebPart, Version=1.0.0.0, Culture=neutral, PublicKeyToken=bbf47e850237632c could not be found or it is not registered as safe."
Alas and anon, it is back to the coding board again! For forsooth, why is my WebPartEditor not displaying?
(sorry, I've been reading "A Connecticut Yankee in King Arthur's Court" of late).
I think you have to just register your dll as safe in Web.config.
Once I entered a value in the Web Part Editor for this, the problem went away. But the question still remains, how to prevent this prior to adding a value via the Web Part Editor? I see no way to give this a default value; I assume even a space (" ") would do the trick...
Could I do something like:
[WebBrowsable(true),
Category("Financial Affairs Forms"),
Personalizable(PersonalizationScope.Shared),
WebDisplayName("WebPart Title Text")]
public string CustomTitleProp { call a custom method to return a space or something; set; }
?
After searching for a long time for a simple way of changing the text color of a #region directive in Visual Studio, I've concluded there is no easy way of doing so.
I know how to change the #region statement color, and how to change the collapsed region color, but I want to change the color of the text with the region description. So:
#region Some text <--- all this text should be in a different color
public void Test()
{
}
#endregion <--- this too
It seems a lot of people are looking for something like this - see How to change the color of expanded regions' titles in VS2008?.
So I've been looking at writing a simple Visual Studio add-in to change the color.
However, it's more complicated than I thought it would be, with classes like Snapshot, Tagger, Classifier, WpfTextViewCreationListener, AdornmentLayer etc.
Simply put, I don't know where to start! I followed a couple of tutorials at the MSDN site, but they seem too complicated for what I'm trying to do.
Can someone point me to the most simple way of doing this? Ie. which classes/methods/events within the VS SDK I should use. I don't mind if the color is not customisable via the UI etc either. I'm using VS2010.
Edit: Just had the mztools website recommended to me; I'll take a look there too. Also noticed that StackOverflow's syntax highlighting of regions is pretty much exactly what I want!
I eventually came up with a solution, at least for VS2010.
Whilst I have used this for coloring '#region' and '#endregion' tags, a similar solution ought to be applicable for any text content in a Visual Studio window.
It seems that this sort of problem can be resolved by creating a IViewTaggerProvider which will 'tag' parts of the source code with a 'classification'.
Visual Studio will provide a style for text tagged with that classification which can then be changed by the user to the desired style via Tools > Options... > Environment > Fonts and Colors.
The Tagger provider looks like:
[Export(typeof(IViewTaggerProvider))]
[ContentType("any")]
[TagType(typeof(ClassificationTag))]
public sealed class RegionTaggerProvider : IViewTaggerProvider
{
[Import]
public IClassificationTypeRegistryService Registry;
[Import]
internal ITextSearchService TextSearchService { get; set; }
public ITagger<T> CreateTagger<T>(ITextView textView, ITextBuffer buffer) where T : ITag
{
if (buffer != textView.TextBuffer)
return null;
var classType = Registry.GetClassificationType("region-foreground");
return new RegionTagger(textView, TextSearchService, classType) as ITagger<T>;
}
}
This creates an ITagger object, which, given a Visual Studio text view, will tag parts of the text with the given classification type. Note that this will work for all text views (i.e. source code editor, 'Find Results' windows etc.). It may be possible to change this by editing the ContentType attribute (to just C#?).
The classification type (in this case "region-foreground") is defined as:
public static class TypeExports
{
[Export(typeof(ClassificationTypeDefinition))]
[Name("region-foreground")]
public static ClassificationTypeDefinition OrdinaryClassificationType;
}
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = "region-foreground")]
[Name("region-foreground")]
[UserVisible(true)]
[Order(After = Priority.High)]
public sealed class RegionForeground : ClassificationFormatDefinition
{
public RegionForeground()
{
DisplayName = "Region Foreground";
ForegroundColor = Colors.Gray;
}
}
The Order attribute determines when the classification will be applied compared to other classifications which may also apply to a span of text.
The DisplayName will be used in the Tools > Options... dialog.
Once the classification is defined, an ITagger class can search a view's text and provide classifications for applicable sections of the text it finds.
Simply put, its job is to listen for the ViewLayoutChanged event of the text view, which is fired when the provided text view's content changes (e.g. because the the user has typed something).
It must then search the text for the area of text it is interested in (called a 'span'). Here, it returns spans of lines containing either #region or #endregion. I've kept this simple, but the TextSearchService used to find matches can also search using regular expressions.
Finally, a method is provided for Visual Studio to retrieve the tags of the text it has found, called GetTags(). For a given span collection, this will return text spans with classification tags, i.e. areas of those spans which should be classified in a certain way.
Its code is:
public sealed class RegionTagger : ITagger<ClassificationTag>
{
private readonly ITextView m_View;
private readonly ITextSearchService m_SearchService;
private readonly IClassificationType m_Type;
private NormalizedSnapshotSpanCollection m_CurrentSpans;
public event EventHandler<SnapshotSpanEventArgs> TagsChanged = delegate { };
public RegionTagger(ITextView view, ITextSearchService searchService, IClassificationType type)
{
m_View = view;
m_SearchService = searchService;
m_Type = type;
m_CurrentSpans = GetWordSpans(m_View.TextSnapshot);
m_View.GotAggregateFocus += SetupSelectionChangedListener;
}
private void SetupSelectionChangedListener(object sender, EventArgs e)
{
if (m_View != null)
{
m_View.LayoutChanged += ViewLayoutChanged;
m_View.GotAggregateFocus -= SetupSelectionChangedListener;
}
}
private void ViewLayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
{
if (e.OldSnapshot != e.NewSnapshot)
{
m_CurrentSpans = GetWordSpans(e.NewSnapshot);
TagsChanged(this, new SnapshotSpanEventArgs(new SnapshotSpan(e.NewSnapshot, 0, e.NewSnapshot.Length)));
}
}
private NormalizedSnapshotSpanCollection GetWordSpans(ITextSnapshot snapshot)
{
var wordSpans = new List<SnapshotSpan>();
wordSpans.AddRange(FindAll(#"#region", snapshot).Select(regionLine => regionLine.Start.GetContainingLine().Extent));
wordSpans.AddRange(FindAll(#"#endregion", snapshot).Select(regionLine => regionLine.Start.GetContainingLine().Extent));
return new NormalizedSnapshotSpanCollection(wordSpans);
}
private IEnumerable<SnapshotSpan> FindAll(String searchPattern, ITextSnapshot textSnapshot)
{
if (textSnapshot == null)
return null;
return m_SearchService.FindAll(
new FindData(searchPattern, textSnapshot) {
FindOptions = FindOptions.WholeWord | FindOptions.MatchCase
});
}
public IEnumerable<ITagSpan<ClassificationTag>> GetTags(NormalizedSnapshotSpanCollection spans)
{
if (spans == null || spans.Count == 0 || m_CurrentSpans.Count == 0)
yield break;
ITextSnapshot snapshot = m_CurrentSpans[0].Snapshot;
spans = new NormalizedSnapshotSpanCollection(spans.Select(s => s.TranslateTo(snapshot, SpanTrackingMode.EdgeExclusive)));
foreach (var span in NormalizedSnapshotSpanCollection.Intersection(m_CurrentSpans, spans))
{
yield return new TagSpan<ClassificationTag>(span, new ClassificationTag(m_Type));
}
}
}
For brevity I have omitted namespaces and the using statements, which are typically of the form Microsoft.VisualStudio.Text.*. For these to be available, the Visual Studio 2010 SDK must first be downloaded.
I've been using this solution for the past few months without issue.
One limitation I noticed is colours are not 'blended', so a color with less than 100% opacity will not 'fade out' the existing colours in a span - which might be useful to preserve syntax highlighting.
I also have little idea of its efficiency, as it looks like it will repeatedly search a document on each keypress. I have not done the research to see if Visual Studio optimises this somehow. I do notice a slowdown of Visual Studio on large files (> ~1000 lines), but I also use Resharper, so I cannot attribute this to this plugin alone.
As this was coded mostly using guesswork, I welcome any comments or code changes which could clarify or simplify things or improve upon the code's performance.
I guess you could start with Visual Studio Add-in project, it will used EnvDTE, which was considered as Visual Studio Object Model, and please find MSDN document here:
http://msdn.microsoft.com/en-us/vstudio/bb968855
You could control your visual studio behavior, like debugger, code editor and so on by EnvDTE.