渲染(重绘)隐形画布 [英] Render (redraw) the invisible canvas

查看:99
本文介绍了渲染(重绘)隐形画布的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个单独的画布标签的应用程序(与他们不同的用户控件)在每一页上像这。现在我需要保存所有网页(画布)为图像。代码是这样的:

 公共静态System.Drawing.Bitmap ExportToImage(帆布油画)
{
//保存旧的背景
刷背景= canvas.Background;
//清除背景,使图像无它
canvas.Background = NULL;

//canvas.UpdateLayout();
//canvas.InvalidateVisual();

//创建一个位图渲染和表面推到它
RenderTargetBitmap renderBitmap =
新RenderTargetBitmap(
(INT)canvas.Width,
( INT)canvas.Height,
96D,
96D,
PixelFormats.Pbgra32);
renderBitmap.Render(画布);

MemoryStream的picStream =新的MemoryStream();
PngBitmapEncoder编码器=新PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
encoder.Save(picStream);

canvas.Background =背景;

返回新System.Drawing.Bitmap(picStream);
}



我做额外的转换,如利润和规模的变化,但它不是。显著



有关画布是活动页面(目前在屏幕上)我得到正常的渲染图像:没有背景,布置位置和大小等)



不过,在没有激活的页面的画布,我得到与原来的帆布外观(背景和没有安排)图像。
我怎么可以强制帆布申请我的修改,并与他们呈现?
我尝试使用UpdateLayout请与InvalidateVisual在画布上,但我没有影响。


解决方案

WPF卸载不可见对象,这意味着当你从一个选项卡浏览了,它会卸载就可以了UI控件和并加载一组新的控制。



要避免此行为,我通常使用的扩展版本的的TabControl 存储每个 ContentPresenter TabItem的当您导航远离它,当你回到那个标签中,重新加载 ContentPresenter 而不是重绘一切。它占用多一点的内存,但是我发现它更好的性能,因为TabItem的不再重新创建它上面的,只要你切换标签的所有控制。



您应该能够使用和基础图像关闭 ContentPresenter 存储每个 TabItem的



原代码是从的此处,虽然网站一直下跌几几个月来,我不知道在哪里把它移动到。我已经改变了这一点,因为我需要允许拖/下降标签项目重新排列它们不需要重画他们,但是这应该不会影响什么。



<预类=郎-CS prettyprint-覆盖> //扩展的TabControl这样你就不会得到
//卸载的性能并切换标签$ b $当重装的VisualTree从而节省显示的项目b
//从http://www.pluralsight-training.net/community/blogs/eburke/archive/2009/04/30/keeping-the-wpf-tab-control-from-destroying-its获得-children.aspx
//,并提出了一些修改,因此做拖/放操作

[TemplatePart(NAME =PART_ItemsHolder时重用TabItem的的ContentPresenter,类型= typeof运算(面板) )
公共类TabControlEx:System.Windows.Controls.TabControl
{
//保存所有项目,但只有标记为可见
私人面板当前选项卡的项目_itemsHolder = NULL ;

// Temporaily持有删除项目的情况下,这是一个拖/放操作
私有对象_deletedObject = NULL;

公共TabControlEx()
:基地()
{
//这是必要的,这样我们得到的初步数据绑定所选项目
this.ItemContainerGenerator .StatusChanged + = ItemContainerGenerator_StatusChanged;
}

///<总结>
///如果容器完成后,产生所选项目
///< /总结>
///< PARAM NAME =发件人>< /参数>
///< PARAM NAME =E>< /参数>
无效ItemContainerGenerator_StatusChanged(对象发件人,EventArgs e)如
{
(this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
this.ItemContainerGenerator.StatusChanged - = ItemContainerGenerator_StatusChanged;
UpdateSelectedItem();
}
}

///<总结>
///得到ItemsHolder和产生任何儿童
///< /总结>
公共覆盖无效OnApplyTemplate()
{
base.OnApplyTemplate();
_itemsHolder = GetTemplateChild(PART_ItemsHolder)作为面板;
UpdateSelectedItem();
}

///<总结>
///当项目改变我们删除任何生成的面板的孩子和添加任何新的必要的
///< /总结>
///< PARAM NAME =E>< /参数>
保护覆盖无效OnItemsChanged(NotifyCollectionChangedEventArgs E)
{
base.OnItemsChanged(E);

如果(_itemsHolder == NULL)
{
的回报;
}

开关(e.Action)
{
情况下NotifyCollectionChangedAction.Reset:
_itemsHolder.Children.Clear();

如果(base.Items.Count大于0)
{
base.SelectedItem = base.Items [0];
UpdateSelectedItem();
}

中断;

情况下NotifyCollectionChangedAction.Add:
情况下NotifyCollectionChangedAction.Remove:

//搜索造成的拖/放操作
如果最近删除的项目( e.NewItems = NULL&放大器;!&安培;!_deletedObject = NULL)
{
的foreach(VAR在e.NewItems项)
{
如果(_deletedObject ==项目)
{
//如果新的项是作为最近删除的一个(即一个拖/放事件)
//然后取消删除并重新使用ContentPresenter所以它不具有相同的为
//重绘。我们确实需要演示(使用标签)
ContentPresenter CP = FindChildContentPresenter(_deletedObject)虽然链接到新的项目;
如果(CP!= NULL)
{
INT指数= _itemsHolder.Children.IndexOf(CP);

(_itemsHolder.Children [指数]的ContentPresenter).TAG =
(产品TabItem的)?项目:(this.ItemContainerGenerator.ContainerFromItem(项目));
}
_deletedObject = NULL;
}
}
}

如果(e.OldItems!= NULL)
{
的foreach(VAR在e.OldItems项)
{

_deletedObject =项目;

//我们希望在稍后的优先级的情况下,运行此此
//是一个拖/放操作,使我们可以重新使用模板
this.Dispatcher。的BeginInvoke(DispatcherPriority.DataBind,
新动作(委托()
{
如果(_deletedObject = NULL)
{
ContentPresenter CP = FindChildContentPresenter(_deletedObject); $! b $ b如果(CP!= NULL)
{
this._itemsHolder.Children.Remove(CP);
}
}
}
) );
}
}

UpdateSelectedItem();
中断;

情况下NotifyCollectionChangedAction.Replace:
抛出新NotImplementedException(替换尚未实现);
}
}

///<总结>
///更新的ItemsHolder
LT的可视子///&; /总结>
///< PARAM NAME =E>< /参数>
保护覆盖无效OnSelectionChanged(SelectionChangedEventArgs E)
{
base.OnSelectionChanged(E);
UpdateSelectedItem();
}

///<总结>
///产生所选项目
///℃的ContentPresenter; /总结>
无效UpdateSelectedItem()
{
如果(_itemsHolder == NULL)
{
的回报;
}

//生成如有必要,
TabItem的项目= GetSelectedTabItem()一个ContentPresenter;
如果(项目!= NULL)
{
CreateChildContentPresenter(项目);
}

//显示右子
的foreach(ContentPresenter孩子_itemsHolder.Children)
{
child.Visibility =((child.Tag作为TabItem的).IsSelected)? Visibility.Visible:Visibility.Collapsed;
}
}

///<总结>
///创建子ContentPresenter为给定的项目(可以是数据或TabItem的)
///< /总结>
///< PARAM NAME =项>< /参数>
///<&回报GT;< /回报>
ContentPresenter CreateChildContentPresenter(对象的项目)
{
如果(项目== NULL)
{
返回NULL;
}

ContentPresenter CP = FindChildContentPresenter(项目);

如果(CP!= NULL)
{
返回CP;
}

//实际儿童要添加。 cp.Tag是对TabItem的
CP =新ContentPresenter基准();
cp.Content =(产品TabItem的)? (项目作为TabItem的).Content:项目;
cp.ContentTemplate = this.SelectedContentTemplate;
cp.ContentTemplateSelector = this.SelectedContentTemplateSelector;
cp.ContentStringFormat = this.SelectedContentStringFormat;
cp.Visibility = Visibility.Collapsed;
cp.Tag =(产品TabItem的)?项目:(this.ItemContainerGenerator.ContainerFromItem(项目));
_itemsHolder.Children.Add(CP);
返回CP;
}

///<总结>
///查找给定对象的CP。数据可能是一个TabItem的或一块数据
的///< /总结>
///< PARAM NAME =数据>< /参数>
///<&回报GT;< /回报>
ContentPresenter FindChildContentPresenter(对象数据)
{
如果(数据TabItem的)
{
数据=(数据截至TabItem的).Content;
}

如果(数据== NULL)
{
返回NULL;
}

如果(_itemsHolder == NULL)
{
返回NULL;
}

的foreach(在_itemsHolder.Children ContentPresenter CP)
{
如果(cp.Content ==数据)
{
返回CP;
}
}

返回NULL;
}

///<总结>
///从TabControl的复制;希望它是在类,而不是私人
///<保护; /总结>
///<&回报GT;< /回报>
保护TabItem的GetSelectedTabItem()
{
对象将selectedItem = base.SelectedItem;
如果(selectedItem设置== NULL)
{
返回NULL;
}

如果(_deletedObject ==将selectedItem)
{

}

TabItem的项目= selectedItem设置为TabItem的;
如果(项目== NULL)
{
项= base.ItemContainerGenerator.ContainerFromIndex(base.SelectedIndex)作为TabItem的;
}
回报率的项目;
}
}



每个注释编辑下面



如果这不会为你工作,你可以尝试渲染你的画布一样正常,再以较低的的DispatcherPriority DispatcherPriority.Render 所以。发生在所有的渲染后打印



事情是这样的:

 公共静态无效ExportToImage(帆布油画,System.Drawing.Bitmap BMP)
{
//保存旧的背景
刷背景= canvas.Background;
//清除背景,使图像无它
canvas.Background = NULL;


//创建一个位图渲染和表面推到它
RenderTargetBitmap renderBitmap =
新RenderTargetBitmap(
(INT)canvas.Width,
(INT)canvas.Height,
96D,
96D,
PixelFormats.Pbgra32);
renderBitmap.Render(画布);

Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
新动作(委托()
{
MemoryStream的picStream =新的MemoryStream();
PngBitmapEncoder编码器=新PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
encoder.Save(picStream);

canvas.Background =背景;

//我不认为你可以简单地返回你的价值在这里,
//所以你可能需要设置别的东西来
//返回你位图调用代码
BMP =新System.Drawing.Bitmap(picStream);
}));
}


I have the tabbed application with a separate canvas (with different UserControls on them) on every page like this. Now I need to save all pages (canvases) to images. The code is like this:

public static System.Drawing.Bitmap ExportToImage(Canvas canvas)
{
    // Save old background
    Brush background = canvas.Background;
    // Clear background to make images free of it
    canvas.Background = null;

    //canvas.UpdateLayout();
    //canvas.InvalidateVisual();

    // Create a render bitmap and push the surface to it
    RenderTargetBitmap renderBitmap =
        new RenderTargetBitmap(
            (int)canvas.Width,
            (int)canvas.Height,
            96d,
            96d,
            PixelFormats.Pbgra32);
    renderBitmap.Render(canvas);

    MemoryStream picStream = new MemoryStream();
    PngBitmapEncoder encoder = new PngBitmapEncoder();
    encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
    encoder.Save(picStream);

    canvas.Background = background;

    return new System.Drawing.Bitmap(picStream);
}

I make additional transformations, such as changing of margins and size, but it's not significant.

For canvas that is on active page (currently on the screen) I get the normal rendered image: no background, arranged position and size etc).

But for canvas on inactive pages I get images with the original canvas look (with background and not arranged). How can I force canvas to apply my modifications and render with them? I try to use UpdateLayout and InvalidateVisual on canvas, but I got no effect.

解决方案

WPF unloads non-visible objects, and this means that when you navigate away from a Tab, it will unload the UI controls on it and and load a new set of controls.

To avoid this behavior, I typically use an extended version of the TabControl that stores the ContentPresenter of each TabItem when you navigate away from it, and when you go back to that tab it reloads the ContentPresenter instead of redrawing everything. It takes up a bit more memory, however I find it better on performance since the TabItem no longer has to re-create all the controls that were on it whenever you switch tabs.

You should be able to use this and base your images off the ContentPresenter stored for each TabItem

The original code is from here, although the site's been down for a few months now and I don't know where it moved to. I've altered it a bit because I needed to allow for dragging/dropping tab items to rearrange them without redrawing them, but that shouldn't affect anything.

// Extended TabControl which saves the displayed item so you don't get the performance hit of 
// unloading and reloading the VisualTree when switching tabs

// Obtained from http://www.pluralsight-training.net/community/blogs/eburke/archive/2009/04/30/keeping-the-wpf-tab-control-from-destroying-its-children.aspx
// and made a some modifications so it reuses a TabItem's ContentPresenter when doing drag/drop operations

[TemplatePart(Name = "PART_ItemsHolder", Type = typeof(Panel))]
public class TabControlEx : System.Windows.Controls.TabControl
{
    // Holds all items, but only marks the current tab's item as visible
    private Panel _itemsHolder = null;

    // Temporaily holds deleted item in case this was a drag/drop operation
    private object _deletedObject = null;

    public TabControlEx()
        : base()
    {
        // this is necessary so that we get the initial databound selected item
        this.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
    }

    /// <summary>
    /// if containers are done, generate the selected item
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
    {
        if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
        {
            this.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged;
            UpdateSelectedItem();
        }
    }

    /// <summary>
    /// get the ItemsHolder and generate any children
    /// </summary>
    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        _itemsHolder = GetTemplateChild("PART_ItemsHolder") as Panel;
        UpdateSelectedItem();
    }

    /// <summary>
    /// when the items change we remove any generated panel children and add any new ones as necessary
    /// </summary>
    /// <param name="e"></param>
    protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
    {
        base.OnItemsChanged(e);

        if (_itemsHolder == null)
        {
            return;
        }

        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Reset:
                _itemsHolder.Children.Clear();

                if (base.Items.Count > 0)
                {
                    base.SelectedItem = base.Items[0];
                    UpdateSelectedItem();
                }

                break;

            case NotifyCollectionChangedAction.Add:
            case NotifyCollectionChangedAction.Remove:

                // Search for recently deleted items caused by a Drag/Drop operation
                if (e.NewItems != null && _deletedObject != null)
                {
                    foreach (var item in e.NewItems)
                    {
                        if (_deletedObject == item)
                        {
                            // If the new item is the same as the recently deleted one (i.e. a drag/drop event)
                            // then cancel the deletion and reuse the ContentPresenter so it doesn't have to be 
                            // redrawn. We do need to link the presenter to the new item though (using the Tag)
                            ContentPresenter cp = FindChildContentPresenter(_deletedObject);
                            if (cp != null)
                            {
                                int index = _itemsHolder.Children.IndexOf(cp);

                                (_itemsHolder.Children[index] as ContentPresenter).Tag =
                                    (item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item));
                            }
                            _deletedObject = null;
                        }
                    }
                }

                if (e.OldItems != null)
                {
                    foreach (var item in e.OldItems)
                    {

                        _deletedObject = item;

                        // We want to run this at a slightly later priority in case this
                        // is a drag/drop operation so that we can reuse the template
                        this.Dispatcher.BeginInvoke(DispatcherPriority.DataBind,
                            new Action(delegate()
                        {
                            if (_deletedObject != null)
                            {
                                ContentPresenter cp = FindChildContentPresenter(_deletedObject);
                                if (cp != null)
                                {
                                    this._itemsHolder.Children.Remove(cp);
                                }
                            }
                        }
                        ));
                    }
                }

                UpdateSelectedItem();
                break;

            case NotifyCollectionChangedAction.Replace:
                throw new NotImplementedException("Replace not implemented yet");
        }
    }

    /// <summary>
    /// update the visible child in the ItemsHolder
    /// </summary>
    /// <param name="e"></param>
    protected override void OnSelectionChanged(SelectionChangedEventArgs e)
    {
        base.OnSelectionChanged(e);
        UpdateSelectedItem();
    }

    /// <summary>
    /// generate a ContentPresenter for the selected item
    /// </summary>
    void UpdateSelectedItem()
    {
        if (_itemsHolder == null)
        {
            return;
        }

        // generate a ContentPresenter if necessary
        TabItem item = GetSelectedTabItem();
        if (item != null)
        {
            CreateChildContentPresenter(item);
        }

        // show the right child
        foreach (ContentPresenter child in _itemsHolder.Children)
        {
            child.Visibility = ((child.Tag as TabItem).IsSelected) ? Visibility.Visible : Visibility.Collapsed;
        }
    }

    /// <summary>
    /// create the child ContentPresenter for the given item (could be data or a TabItem)
    /// </summary>
    /// <param name="item"></param>
    /// <returns></returns>
    ContentPresenter CreateChildContentPresenter(object item)
    {
        if (item == null)
        {
            return null;
        }

        ContentPresenter cp = FindChildContentPresenter(item);

        if (cp != null)
        {
            return cp;
        }

        // the actual child to be added.  cp.Tag is a reference to the TabItem
        cp = new ContentPresenter();
        cp.Content = (item is TabItem) ? (item as TabItem).Content : item;
        cp.ContentTemplate = this.SelectedContentTemplate;
        cp.ContentTemplateSelector = this.SelectedContentTemplateSelector;
        cp.ContentStringFormat = this.SelectedContentStringFormat;
        cp.Visibility = Visibility.Collapsed;
        cp.Tag = (item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item));
        _itemsHolder.Children.Add(cp);
        return cp;
    }

    /// <summary>
    /// Find the CP for the given object.  data could be a TabItem or a piece of data
    /// </summary>
    /// <param name="data"></param>
    /// <returns></returns>
    ContentPresenter FindChildContentPresenter(object data)
    {
        if (data is TabItem)
        {
            data = (data as TabItem).Content;
        }

        if (data == null)
        {
            return null;
        }

        if (_itemsHolder == null)
        {
            return null;
        }

        foreach (ContentPresenter cp in _itemsHolder.Children)
        {
            if (cp.Content == data)
            {
                return cp;
            }
        }

        return null;
    }

    /// <summary>
    /// copied from TabControl; wish it were protected in that class instead of private
    /// </summary>
    /// <returns></returns>
    protected TabItem GetSelectedTabItem()
    {
        object selectedItem = base.SelectedItem;
        if (selectedItem == null)
        {
            return null;
        }

        if (_deletedObject == selectedItem)
        { 

        }

        TabItem item = selectedItem as TabItem;
        if (item == null)
        {
            item = base.ItemContainerGenerator.ContainerFromIndex(base.SelectedIndex) as TabItem;
        }
        return item;
    }
}

Edit per comments below

If this doesn't work for you, you can try rendering your Canvas like normal, then printing it at a lower DispatcherPriority than DispatcherPriority.Render so it prints after all the rendering has occurred.

Something like this:

public static void ExportToImage(Canvas canvas, System.Drawing.Bitmap bmp)
{
    // Save old background
    Brush background = canvas.Background;
    // Clear background to make images free of it
    canvas.Background = null;


    // Create a render bitmap and push the surface to it
    RenderTargetBitmap renderBitmap =
        new RenderTargetBitmap(
            (int)canvas.Width,
            (int)canvas.Height,
            96d,
            96d,
            PixelFormats.Pbgra32);
    renderBitmap.Render(canvas);

    Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, 
    new Action(delegate() 
    {
        MemoryStream picStream = new MemoryStream();
        PngBitmapEncoder encoder = new PngBitmapEncoder();
        encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
        encoder.Save(picStream);

        canvas.Background = background;

        // I don't think you can simply return your value here, 
        // so you'll probably need to setup something else to 
        // return your bitmap to your calling code
        bmp = new System.Drawing.Bitmap(picStream);
    }));
}

这篇关于渲染(重绘)隐形画布的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆