9

바이너리 데이터를 XML로 변환하고 기본 VS XML 편집기 및 XML 언어 서비스에서 열어 저장 한 다음 다시 이진 데이터로 변환하는 VS2017 (C#에서) 용 확장 VSPackage를 만들려고합니다.VSX : XML로 변환 된 바이너리 파일을 처리하기 위해 기존 XML 편집기를 어떻게 재사용 할 수 있습니까?

그러나이 단계에서 어떤 단계를 거쳐야 하는지를 정리하는 데 어려움이 있습니다. 핵심 편집기를 만들기

  • 변환 된 XML 데이터로 피드

    • 것은 새로 만들기 텍스트 버퍼
    • 텍스트로 피드 : 나는 편집기 공장에서 새로운 에디터를 만들 때 지금 다음 생각 버퍼

    이 지금 내 시도는 다음과 같습니다

    private MyPackage _package; // Filled via constructor 
    private IServiceProvider _serviceProvider; // Filled via SetSite 
    
    public int CreateEditorInstance(uint grfCreateDoc, string pszMkDocument, string pszPhysicalView, 
        IVsHierarchy pvHier, uint itemid, IntPtr punkDocDataExisting, out IntPtr ppunkDocView, 
        out IntPtr ppunkDocData, out string pbstrEditorCaption, out Guid pguidCmdUI, out int pgrfCDW) 
    { 
        // Initialize and validate parameters. 
        ppunkDocView = IntPtr.Zero; 
        ppunkDocData = IntPtr.Zero; 
        pbstrEditorCaption = String.Empty; 
        pguidCmdUI = Guid.Empty; 
        pgrfCDW = 0; 
        VSConstants.CEF createDocFlags = (VSConstants.CEF)grfCreateDoc; 
        if (!createDocFlags.HasFlag(VSConstants.CEF.OpenFile) && !createDocFlags.HasFlag(VSConstants.CEF.Silent)) 
         return VSConstants.E_INVALIDARG; 
        if (punkDocDataExisting != IntPtr.Zero) 
         return VSConstants.VS_E_INCOMPATIBLEDOCDATA; 
    
        // Create a sited IVsTextBuffer storing the converted data with the XML data and language service set. 
        IVsTextLines textLines = _package.CreateComInstance<VsTextBufferClass, IVsTextLines>(); 
        SiteObject(textLines); 
        string xmlText = BinaryXmlData.GetXmlString(pszMkDocument); 
        textLines.InitializeContent(xmlText, xmlText.Length); 
        ErrorHandler.ThrowOnFailure(textLines.SetLanguageServiceID(ref Guids.XmlLanguageServiceGuid)); 
    
        // Instantiate a sited IVsCodeWindow and feed it with the text buffer. 
        IVsCodeWindow codeWindow = _package.CreateComInstance<VsCodeWindowClass, IVsCodeWindow>(); 
        SiteObject(codeWindow); 
        codeWindow.SetBuffer(textLines); 
    
        // Return the created instances to the caller. 
        ppunkDocView = Marshal.GetIUnknownForObject(codeWindow); 
        ppunkDocData = Marshal.GetIUnknownForObject(textLines); 
    
        return VSConstants.S_OK; 
    } 
    
    private void SiteObject(object obj) 
    { 
        (obj as IObjectWithSite)?.SetSite(_serviceProvider); 
    } 
    
    // --- CreateComInstance is a method on my package ---- 
    internal TInterface CreateComInstance<TClass, TInterface>() 
    { 
        Guid guidT = typeof(TClass).GUID; 
        Guid guidInterface = typeof(TInterface).GUID; 
    
        TInterface instance = (TInterface)CreateInstance(ref guidT, ref guidInterface, typeof(TInterface)); 
        if (instance == null) 
         throw new COMException($"Could not instantiate {typeof(TClass).Name}/{typeof(TInterface).Name}."); 
    
        return instance; 
    } 
    

    명시 적으로 편집기로 파일을 열려고하면 ""이 선택된 편집기로 열 수 없습니다. 다른 편집기를 선택하십시오. "메시지가 이해가 안되며 XML 편집기를 사용하여 XML 데이터를 열려고했지만 이진 데이터로 텍스트 편집기를 열려고합니다.

    내가 여기에 붙어있어, 그랬어. 내가 생각할 수있는 모든는 변환 된 데이터를 공급합니다. 분명히이 방법이 올바른 일이 아니다.

    • 을 어떻게 빨리, 바이너리 데이터를 가져 XML로 변환 한 다음 XML로 먹이를 inbetween 단계를 추가 할 수 있습니다 편집기?
    • XML 편집기가 파일을 저장할 때 어떻게 다시 이진 파일로 저장하겠습니까?
    • XML을 다시 사용할 수 있습니까? 편집자 및 언어 서비스

    이러한 질문에 긴 답변이 필요하면 죄송합니다. 나는 옳은 방향으로 또는 이미 오픈 소스 확장이 가리키는 것을 얻을 수 있다면 비슷한 일을 할 수있다. (VS 코드 편집기에서 파일 데이터를 표시하기 전에 파일 데이터를 변환하는 것).

  • +0

    잘 모르겠지만 임시 디렉토리에 xml 파일로 저장하여 파일을 열어서 파일을보고 변경. 우아하지는 않지만 신속하게 할 수 있습니다. 어쩌면 누군가 우아한 경로를 작동시키는 방법을 알고있을 것입니다 ... – Will

    +0

    그래, VSX로 구타당한 느낌에 대해서도 생각했지만 파일을 수정하기 위해 현재의 외부 프로그램을 유지할 수 있습니다. 게다가 그 해킹이 결국 구현하기가 쉽지 않은지 ... –

    답변

    5

    일반적인 생각은 일반적으로 Xml 편집기가 수행하도록하는 것입니다. 문서를 엽니 다.

    내가 올바르게 이해한다면 실제 XML 문서가 없으므로 문서를 만들어야합니다. 문서는 Visual Studio의 Running Object Table에 등록 된 내용입니다 (실제 파일 일 필요는 없습니다).

    일단 문서가 있으면 셸에 열어달라고 요청하면됩니다. ROT를 다시 사용하여 BeforeSave and AfterSave events을 처리 할 수 ​​있습니다. 다음은이 모든 작업을 수행해야하는 몇 가지 샘플 코드입니다.

    public int CreateEditorInstance(uint grfCreateDoc, string pszMkDocument, string pszPhysicalView, IVsHierarchy pvHier, uint itemid, IntPtr punkDocDataExisting, out IntPtr ppunkDocView, out IntPtr ppunkDocData, out string pbstrEditorCaption, out Guid pguidCmdUI, out int pgrfCDW) 
    { 
        ppunkDocView = IntPtr.Zero; 
        ppunkDocData = IntPtr.Zero; 
        pbstrEditorCaption = null; 
        pguidCmdUI = Guid.Empty; 
        pgrfCDW = 0; 
    
        // create your virtual Xml buffer 
        var data = Package.CreateComInstance<VsTextBufferClass, IVsTextLines>(); 
        SiteObject(data); 
    
        // this is where you're supposed to build your virtual Xml content from your binary data 
        string myXml = "<root>blah</root>"; 
        data.InitializeContent(myXml, myXml.Length); 
        var dataPtr = Marshal.GetIUnknownForObject(data); 
    
        // build a document and register it in the Running Object Table 
        // this document has no hierarchy (it will be handled by the 'Miscellaneous Files' fallback project) 
        var rotFlags = _VSRDTFLAGS.RDT_ReadLock | _VSRDTFLAGS.RDT_VirtualDocument; 
    
        // come up with a moniker (which will be used as the caption also by the Xml editor) 
        // Note I presume the moniker is a file path, wich may not always be ok depending on your context 
        var virtualMk = Path.ChangeExtension(pszMkDocument, ".xml"); 
        var rot = (IVsRunningDocumentTable)_sp.GetService(typeof(SVsRunningDocumentTable)); 
        int hr = rot.RegisterAndLockDocument((uint)rotFlags, virtualMk, null, VSConstants.VSITEMID_NIL, dataPtr, out uint docCookie); 
        if (hr != 0) 
         return hr; 
    
        try 
        { 
         // ask Visual Studio to open that document 
         var opener = (IVsUIShellOpenDocument)_sp.GetService(typeof(SVsUIShellOpenDocument)); 
         var view = VSConstants.LOGVIEWID_Primary; 
         opener.OpenDocumentViaProject(virtualMk, ref view, 
          out Microsoft.VisualStudio.OLE.Interop.IServiceProvider psp, 
          out IVsUIHierarchy uiHier, 
          out uint id, 
          out IVsWindowFrame frame); 
         if (frame != null) 
         { 
          // Hmm.. the dirty bit (the star after the caption) is not updated by the Xml Editor... 
          // If you close the document (or close VS), it does update it, but it does not react when we type in the editor. 
          // This is unexpected, so, let's do the "dirty" work ourselves 
          // hook on text line events from the buffer 
          var textLineEvents = new TextLineEvents((IConnectionPointContainer)data); 
    
          // we want to know when to unadvise, to hook frame events too 
          ((IVsWindowFrame2)frame).Advise(textLineEvents, out uint frameCookie); 
    
          textLineEvents.LineTextChanged += (sender, e) => 
          { 
           // get the dirty bit and override the frame's dirty state 
           ((IVsPersistDocData)data).IsDocDataDirty(out int dirty); 
           frame.SetProperty((int)__VSFPROPID2.VSFPROPID_OverrideDirtyState, dirty != 0 ? true : false); 
          }; 
    
          // now handle save events using the rot 
          var docEventHandler = new RotDocumentEvents(docCookie); 
          docEventHandler.Saving += (sender, e) => 
          { 
           // this is where you can get the content of the data and save your binary data back 
           // you can use Saved or Saving 
    
          }; 
    
          docEventHandler.Saved += (sender, e) => 
          { 
           // manual reset of dirty bit... 
           frame.SetProperty((int)__VSFPROPID2.VSFPROPID_OverrideDirtyState, false); 
          }; 
          rot.AdviseRunningDocTableEvents(docEventHandler, out uint rootCookie); 
    
          frame.Show(); 
         } 
        } 
        finally 
        { 
         rot.UnlockDocument((uint)_VSRDTFLAGS.RDT_ReadLock, docCookie); 
        } 
        return VSConstants.S_OK; 
    } 
    
    private class TextLineEvents : IVsTextLinesEvents, IVsWindowFrameNotify, IVsWindowFrameNotify2 
    { 
        public event EventHandler LineTextChanged; 
        private uint _cookie; 
        private IConnectionPoint _cp; 
    
        public TextLineEvents(IConnectionPointContainer cpc) 
        { 
         var textLineEventsGuid = typeof(IVsTextLinesEvents).GUID; 
         cpc.FindConnectionPoint(ref textLineEventsGuid, out _cp); 
         _cp.Advise(this, out _cookie); 
        } 
    
        public void OnChangeLineText(TextLineChange[] pTextLineChange, int fLast) => LineTextChanged?.Invoke(this, EventArgs.Empty); 
    
        public int OnClose(ref uint pgrfSaveOptions) 
        { 
         _cp.Unadvise(_cookie); 
         return VSConstants.S_OK; 
        } 
    
        public void OnChangeLineAttributes(int iFirstLine, int iLastLine) { } 
        public int OnShow(int fShow) => VSConstants.S_OK; 
        public int OnMove() => VSConstants.S_OK; 
        public int OnSize() => VSConstants.S_OK; 
        public int OnDockableChange(int fDockable) => VSConstants.S_OK; 
    } 
    
    private class RotDocumentEvents : IVsRunningDocTableEvents3 
    { 
        public event EventHandler Saved; 
        public event EventHandler Saving; 
    
        public RotDocumentEvents(uint docCookie) 
        { 
         DocCookie = docCookie; 
        } 
    
        public uint DocCookie { get; } 
    
        public int OnBeforeSave(uint docCookie) 
        { 
         if (docCookie == DocCookie) 
         { 
          Saving?.Invoke(this, EventArgs.Empty); 
         } 
         return VSConstants.S_OK; 
        } 
    
        public int OnAfterSave(uint docCookie) 
        { 
         if (docCookie == DocCookie) 
         { 
          Saved?.Invoke(this, EventArgs.Empty); 
         } 
         return VSConstants.S_OK; 
        } 
    
        public int OnAfterFirstDocumentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) => VSConstants.S_OK; 
        public int OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) => VSConstants.S_OK; 
        public int OnAfterAttributeChange(uint docCookie, uint grfAttribs) => VSConstants.S_OK; 
        public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame) => VSConstants.S_OK; 
        public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame) => VSConstants.S_OK; 
        public int OnAfterAttributeChangeEx(uint docCookie, uint grfAttribs, IVsHierarchy pHierOld, uint itemidOld, string pszMkDocumentOld, IVsHierarchy pHierNew, uint itemidNew, string pszMkDocumentNew) => VSConstants.S_OK; 
    } 
    
    +0

    와우, 완벽하게 작동합니다!작은 질문이 하나 남았습니다. 편집기 제목에서 "저장되지 않은 변경 사항"별표를 사용할 수 있습니까? 그리고 :이 모든 것에 대해 어떻게 알게 되었습니까? 내가 읽어야 할 문학은 무엇입니까? : D –

    +0

    당신 말이 맞아요. 예상치 못한 것입니다. 실제 파일이 아니기 때문일 수 있습니다. 버퍼에 더티 비트가 설정되어 있지만 XmlEditor는 텍스트를 변경할 때 사용하지 않습니다 (파일/프로젝트/솔루션을 닫을 때 작동 함, 경고 메시지가 표시되고 *가 설정 됨). 해결 방법을 추가했습니다 ... VS 개발과 관련하여 officiel SDK (풀) 이외의 특정 문헌은 없습니다. 그냥 경험 (그리고 반사경, ILSpy 등 도구를 사용하여 Visual Studio 어셈블리에서 많은 spelunking :-) 이것은 "오래된"인터페이스입니다. 새로운 WPF 외. 더 낫다. –

    +0

    그건 분명히 이상한 행동입니다. 매분마다 해결책을 찾아 봅니다. 그동안 VS가 어떤 불가사의 한 이유로 VS에서 XML 내용을 가진 Temp.txt를 현재 작업중인 디렉토리에 쓰고 싶었던 것처럼 버그를 찾아 내려고했습니다. 메시지 상자가 나타나서 뭔가 저장할 수 없었습니다. C : \ Windows \ system32 \ Temp.txt 또는 VS devenv.exe 경로. 나는 분명히 그런 코드를 추가하지 않았고 메모리로 변환했다. 그리고 저장과 저장 사이에 일어난 것처럼 보인다. 어쩌면 나는 현재 디렉토리를 실제 임시 경로로 변경하려고 시도 할 것이다 ... : O Bounty는 매우 빨리 수여된다. 멀리! –