Как узнать, является ли активный документ текстовым документом?

Я разрабатываю расширение Visual Studio, в котором одна из реализованных команд должна быть доступна только тогда, когда активный документ является текстовым документом (например, «Toggle Bookmark» Visual Studio). Проблема в том, что я не могу понять, как сказать, когда это так.

Сейчас у меня есть полурабочее решение. В методе Initialize пакета я подписываюсь на событие WindowActivated DTE, а затем всякий раз, когда активируется окно, я проверяю, имеет ли свойство окна DocumentData тип TextDocument:

protected override void Initialize()
{
    base.Initialize();

    var dte = Package.GetGlobalService(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
    dte.Events.WindowEvents.WindowActivated += WindowEventsOnWindowActivated;

    //More initialization here...
}

//This is checked from command's BeforeQueryStatus
public bool ActiveDocumentIsText { get; private set; } = false;

private void WindowEventsOnWindowActivated(Window gotFocus, Window lostFocus)
{
    if (gotFocus.Kind != "Document")                
        return; //It's not a document (e.g. it's a tool window)

    TextDocument textDoc = gotFocus.DocumentData as TextDocument;
    ActiveDocumentIsText = textDoc != null;
}

Проблема с этим подходом заключается в том, что 1) Window.DocumentData задокументирован как «только для внутреннего использования .NET Framework», и 2) это дает ложное срабатывание, когда документ, который имеет как представление кода, так и представление дизайна (например, файл .visxmanifest) открыт в режиме конструктора.

Я пытался использовать IVsTextManager.GetActiveView, но это возвращает последнее открытое активное текстовое представление, поэтому, если я открою файл .txt, а затем файл .png, он вернет данные для файла .txt, даже если это больше не активный документ.

Итак, как мне проверить, является ли активный документ текстовым документом или представлением кода документа, у которого может быть дизайнер... и, если возможно, не использовать "недокументированные" классы/члены?

ОБНОВЛЕНИЕ: я нашел немного лучшее решение. Внутри обработчика активации окна:

ActiveDocumentIsText = gotFocus.Document.Object("TextDocument") != null;

По крайней мере, это правильно задокументировано, но я до сих пор есть проблема ложных срабатываний с дизайнерами.


person Konamiman    schedule 10.10.2017    source источник


Ответы (1)


Наконец-то я понял. Это несколько сложно, но работает и на 100% «легально». Вот рецепт:

1- Сделайте класс пакета реализующим IVsRunningDocTableEvents. Сделайте все методы просто return VSConstants.S_OK;

2- Добавьте в класс пакета следующее поле и следующий вспомогательный метод:

private IVsRunningDocumentTable runningDocumentTable;

private bool DocIsOpenInLogicalView(string path, Guid logicalView, out IVsWindowFrame windowFrame)
{
    return VsShellUtilities.IsDocumentOpen(
        this,
        path,
        VSConstants.LOGVIEWID_TextView,
        out var dummyHierarchy2, out var dummyItemId2,
        out windowFrame);
}

3- Добавьте следующее в метод Initialize класса пакета:

runningDocumentTable = GetService(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable;
runningDocumentTable.AdviseRunningDocTableEvents(this, out var dummyCookie);

4- Не моргайте, вот и волшебство! Реализуйте метод IVsRunningDocTableEvents.OnBeforeDocumentWindowShow следующим образом:

public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame)
{
    runningDocumentTable.GetDocumentInfo(docCookie,
        out var dummyFlags, out var dummyReadLocks, out var dummyEditLocks,
        out string path, 
        out var dummyHierarchy, out var dummyItemId, out var dummyData);

    IVsWindowFrame windowFrameForTextView;
    var docIsOpenInTextView =
        DocIsOpenInLogicalView(path, VSConstants.LOGVIEWID_Code, out windowFrameForTextView) ||
        DocIsOpenInLogicalView(path, VSConstants.LOGVIEWID_TextView, out windowFrameForTextView);

    //Is the document open in the code/text view,
    //AND the window for that view is the one that has been just activated?

    ActiveDocumentIsText = docIsOpenInTextView && pFrame == logicalViewWindowFrame;

    return VSConstants.S_OK;
} 
person Konamiman    schedule 12.10.2017
comment
Похоже, что DocIsOpenInLogicalView не использует параметр logicalView, поэтому похоже, что требуется только один вызов VsShellUtilities.IsDocumentOpen с LOGVIEWID_TextView. Но этот ответ лучше всего, что я нашел, чтобы дать подсказки о том, как использовать эти руководства! - person DavidS; 13.12.2019