Скопируйте элемент пользовательского интерфейса с Adorner

Я работаю над созданием скриншота элемента пользовательского интерфейса (WPF) различного размера, и я могу добиться этого с помощью "RenderTargetBitmap. Но UIElement который имеет Adorner часть не приходит, пока беру копию. Что я должен сделать, чтобы достичь этого. Любая ссылка или фрагмент кода?

3 ответа

Решение

Насколько я знаю, элементы не имеют прямых ссылок на своих украшателей. Тем не менее, украшатели ссылаются на свой элемент через AdornedElement, поэтому вы можете искать украшатели, назначенные вашему элементу, вот так:

var layer = AdornerLayer.GetAdornerLayer(element);
var adorners = layer.GetVisualChildren().Cast<Adorner>().Where(a => a.AdornedElement == element);

Вот GetVisualChildren это метод расширения, который определяется как:

public static IEnumerable<DependencyObject> GetVisualChildren(this DependencyObject current) {
    return Enumerable.Range(0, VisualTreeHelper.GetChildrenCount(current)).Select(i => VisualTreeHelper.GetChild(current, i));
}

Размер рекламного элемента, кажется, включает в себя размер украшенного элемента (хотя я не уверен, что это всегда так), поэтому, если есть только один рекламный элемент, это ваш размер скриншота. Если существует несколько рекламодателей, вам нужно будет найти максимум для каждой границы (слева, сверху, справа, снизу), чтобы вычислить область снимка экрана.

Вам нужно будет захватить AdornerDecorator который содержит как украшенные элементы и AdornerLayer (layer в приведенном выше коде). Это будет визуальный родительский слой:

var container = VisualTreeHelper.GetParent(layer) as Visual;

Как только у вас есть контейнер, вы можете сделать это с помощью RenderTargetBitmap и обрезать его до области скриншота.

Для области скриншота вам нужны границы элемента относительно контейнера. Сначала получите не относительные границы:

var elementBounds = element.RenderTransform.TransformBounds(new Rect(element.RenderSize));

Затем получите эти границы относительно контейнера:

var relativeElementBounds = element.TransformToAncestor(container).TransformBounds(elementBounds);

Как я упоминал выше, вам нужно будет сделать это для элемента, а также для каждого из его украшений и объединить максимальные границы в один окончательный Rect, который достаточно велик, чтобы вместить все из них.

Наконец, используйте CroppedBitmap, чтобы получить обрезанную версию RenderTargetBitmap:

var croppedBitmap = new CroppedBitmap(renderTargetBitmap, new Int32Rect(left, top, width, height));

CroppedBitmap а также RenderTargetBitmap оба наследуют от BitmapSourceТаким образом, вы должны быть в состоянии сохранить его таким же образом.

Вы можете использовать собственное пространство имен WPF Printing для печати в файл XPS, и это будет включать в себя рекламное объявление в результате (я успешно его протестировал)...

using System.Windows.Controls;
private void ExecutePrintCommand(object obj)
{
    PrintDialog printDialog = new PrintDialog();
    if (printDialog.ShowDialog() == true)
    {
        printDialog.PrintVisual(_mainWindow, "Main Window with Adorner");
    }
}

Если вы не хотите использовать PrintDialog (который фактически открывает диалоговое окно). Вы можете использовать класс XpsDocumentWriter для программного управления процессом. Включающий фрагмент для этого...

     XpsDocumentWriter xpsdw = PrintQueue.CreateXpsDocumentWriter(q);
     xpsdw.Write(viewer.Document);

... который был извлечен отсюда: печатать FixedDocument программно И есть еще статьи о тонкой настройке процесса, если это является частью ваших требований. Обратите внимание, что файл XPS на самом деле представляет собой файл "zip", маскирующийся под файл "xps", так что вы можете разархивировать его, изменив расширение, чтобы увидеть, имеет ли содержимое какое-либо применение.

Во-вторых, я протестировал сохранение окна с надписью в TextBox с этим кодом...

    private void SaveWithAdorner()
    {
        RenderTargetBitmap rtb = RenderVisaulToBitmap(_mainWindow, 500, 300);
        MemoryStream file = new MemoryStream();
        BitmapEncoder encoder = new PngBitmapEncoder();
        encoder.Frames.Add(BitmapFrame.Create(rtb));
        encoder.Save(file);
        using (FileStream fstream = File.OpenWrite("Myimage.jpg"))
        {
            file.WriteTo(fstream);
            fstream.Flush();
            fstream.Close();
        }
    }

... с хорошими результатами. Т.е. в сохраненном растровом изображении появился рекламодатель с красной рамкой. Это может отличаться от вашего кода, потому что я использую кодировщик Png (но сохраненный в файл 'jpg').

Хотя я успешно проверил оба подхода, вам нужно проверить их на своем оборудовании.

И, наконец, как последнее средство, вы можете отключить режим аппаратного рендеринга WPF и установить его в программный рендеринг...

RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly;

... для которого есть хороший SO поток здесь: Режим рендеринга программного обеспечения - WPF

В моем случае все, что мне было нужно, это вызвать класс AdornerLayer следующим образом:

    public void GetScreenshotWithAdorner(Canvas canvas, string filename)
    {
      AdornerLayer adornerlayer = AdornerLayer.GetAdornerLayer(canvas);

      RenderTargetBitmap rtb = new RenderTargetBitmap(
        (int)canvas.ActualWidth,
        (int)canvas.ActualHeight,
        96, //dip X
        96, //dpi Y
        PixelFormats.Pbgra32);
      rtb.Render(canvas); //renders the canvas screen first...
      rtb.Render(adornerlayer); //... then it renders the adorner layer

      SaveRTBAsPNG(rtb, filename);
    }

    private void SaveRTBAsPNG(RenderTargetBitmap bmp, string filename)
    {
      PngBitmapEncoder pngImage = new PngBitmapEncoder();

      pngImage.Frames.Add(BitmapFrame.Create(bmp));
      using (var filestream = System.IO.File.Create(filename))
      {
        pngImage.Save(filestream);
      }
    }

Это работает, если вы хотите включить ВСЕ украшения на свой холст.

Другие вопросы по тегам