C# - OpenXML SDK 2.5 - Insert New Slide from Slide Masters with the layout that contains images

 ̄綄美尐妖づ 提交于 2020-01-07 06:57:27

问题


I used this [https://codingthedocument.wordpress.com/author/agschwantes/] to create my new slides with the help of the OpenXML 2.5 SDK. I designed and used my own Slide Master to create a new slide. My Slide Master includes some layouts with images and some layouts without images.

If I create a slide from my Master Layout without images, everything works fine. If I create a slide with the layout, that contains images, I get the right layout BUT on top of every fixed images there is another movable image overlapping the fixed one, so there are unnecessary duplicates of fixed images, that I don't need in my new created slide.

How can I solve this problem?

My code is below:

       public static void InsertNewSlide(string presentationFile, int position, string layoutName)
      {
        using (PresentationDocument presentationDocument = PresentationDocument.Open(presentationFile, true))
        {

            InsertNewSlide(presentationDocument, position, layoutName);
        }
      }

    public static void InsertNewSlide(PresentationDocument presentationDocument, int position, string layoutName)
    {
        PresentationPart presentationPart = presentationDocument.PresentationPart;

        OpenXML.Slide slide = new OpenXML.Slide(new CommonSlideData(new ShapeTree()));

        SlidePart slidePart = presentationPart.AddNewPart<SlidePart>();

        slide.Save(slidePart);

        SlideMasterPart slideMasterPart = presentationPart.SlideMasterParts.First();

        SlideLayoutPart slideLayoutPart = slideMasterPart.SlideLayoutParts.SingleOrDefault(sl => sl.SlideLayout.CommonSlideData.Name.Value.Equals(layoutName, StringComparison.OrdinalIgnoreCase));

        slidePart.AddPart<SlideLayoutPart>(slideLayoutPart);

        slidePart.Slide.CommonSlideData = (CommonSlideData)slideMasterPart.SlideLayoutParts.SingleOrDefault(sl => sl.SlideLayout.CommonSlideData.Name.Value.Equals(layoutName)).SlideLayout.CommonSlideData.Clone();

        using (Stream stream = slideLayoutPart.GetStream())
        {
            slidePart.SlideLayoutPart.FeedData(stream);

        }

        foreach (ImagePart iPart in slideLayoutPart.ImageParts)
        {
             ImagePart newImagePart = slidePart.AddImagePart(iPart.ContentType, slideLayoutPart.GetIdOfPart(iPart));
                                                        newImagePart.FeedData(iPart.GetStream());
        }

        uint maxSlideId = 1;
        SlideId prevSlideId = null;
        var slideIdList = presentationPart.Presentation.SlideIdList;
        foreach (SlideId slideId in slideIdList.ChildElements)
        {
            if (slideId.Id > maxSlideId)
            {
                maxSlideId = slideId.Id;
            }

            position--;
            if (position == 0)
            {
                prevSlideId = slideId;
            }

        }
        maxSlideId++;
        SlideId newSlideId = slideIdList.InsertAfter(new SlideId(), prevSlideId);
        newSlideId.Id = maxSlideId;
        newSlideId.RelationshipId = presentationPart.GetIdOfPart(slidePart);

        presentationPart.Presentation.Save();
    }

}

回答1:


I think you should leave out the foreach loop that copies the image parts. When I normally copy slides from a slidemaster I use a similar code setup that you are using, but without the foreach. It then copies the given slide from the slidemaster part including all images, layout, etc.

The code I use in one of my projects is listed below (the SetTitle(string) call is to an external method, and I'm using a hard-coded position in the SlideMasterPart as opposed to your string based layout name.

public static void InsertNewSlideB(PresentationDocument presentationDocument, int position, string slideTitle)
{
    if (presentationDocument == null)
    {
        throw new ArgumentNullException("presentationDocument");
    }

    if (slideTitle == null)
    {
       throw new ArgumentNullException("slideTitle");
    }

    PresentationPart presentationPart = presentationDocument.PresentationPart;

    // Verify that the presentation is not empty.
    if (presentationPart == null)
    {
        throw new InvalidOperationException("The presentation document is empty.");
    }

    // Declare and instantiate a new slide.
    Slide slide = new Slide(new CommonSlideData(new ShapeTree()));

    SlidePart slidePart = presentationPart.AddNewPart<SlidePart>();
    slide.Save(slidePart);

    SlideLayoutPart layoutPart = presentationPart.SlideMasterParts.ElementAt(0).SlideLayoutParts.ElementAt(1);
    slidePart.AddPart<SlideLayoutPart>(layoutPart);

    slidePart.Slide.CommonSlideData = (CommonSlideData)layoutPart.SlideLayout.CommonSlideData.Clone();

    SetTitle(slidePart, slideTitle);

    SlideIdList slideIdList = presentationPart.Presentation.SlideIdList;

    // Find the highest slide ID in the current list.
    uint maxSlideId = 1;
    SlideId prevSlideId = null;

    foreach (SlideId slideId in slideIdList.ChildElements)
     {
        if (slideId.Id > maxSlideId)
        {
            maxSlideId = slideId.Id;
        }

        position--;
        if (position == 0)
        {
            prevSlideId = slideId;
        }
    }

    maxSlideId++;

    // Insert the new slide into the slide list after the previous slide.
    SlideId newSlideId = slideIdList.InsertAfter(new SlideId(), prevSlideId);
    newSlideId.Id = maxSlideId;
    newSlideId.RelationshipId = presentationPart.GetIdOfPart(slidePart);

    // Save the modified presentation.
    presentationPart.Presentation.Save();
}



回答2:


Years later, but to help others - there is no need to copy nodes which are not serving a placeholder function to the new slide being based on the master slide template (including images).

The line

using (Stream stream = slideLayoutPart.GetStream())
{
    slidePart.SlideLayoutPart.FeedData(stream);

obviously assumes you are not adding a slide within 1 working stream (that is you have separate streams for reading and writing), and the equivalent line

slidePart.Slide.CommonSlideData = (CommonSlideData)layoutPart.SlideLayout.CommonSlideData.Clone();

Both of these lines of code copy all the master data as an overlay onto the new slide . As an example, run one of the blocks of code from the answers above, open the resulting .pptx file in a presentation manager and delete whatever shapes you see - you will note each item is sitting on top of an identical copy (the master slide version) which you cannot delete. This method therefore bloats the file unnecessarily and makes working with the .pptx messy and not as predicted for the end user.

the code below is working including with images

public static SlidePart AppendNewSlide(PresentationPart presentationPart, SlideLayoutPart masterLayoutPart, out IEnumerable<Shape> placeholderShapes)
{
    Slide clonedSlide = new Slide() {
        ColorMapOverride = new ColorMapOverride {
            MasterColorMapping = new Draw.MasterColorMapping()
        }
    };

    SlidePart clonedSlidePart = presentationPart.AddNewPart<SlidePart>();
    clonedSlidePart.Slide = clonedSlide;
    clonedSlidePart.AddPart(masterLayoutPart);
    clonedSlide.Save(clonedSlidePart);

    var masterShapeTree = masterLayoutPart.SlideLayout.CommonSlideData.ShapeTree;

    placeholderShapes = (from s in masterShapeTree.ChildElements<Shape>()
                           where s.NonVisualShapeProperties.OfType<ApplicationNonVisualDrawingProperties>().Any(anvdp=>anvdp.PlaceholderShape != null)
                           select new Shape()
                           {
                               NonVisualShapeProperties = (NonVisualShapeProperties)s.NonVisualShapeProperties.CloneNode(true),
                               TextBody = new TextBody(s.TextBody.ChildElements<Draw.Paragraph>().Select(p => p.CloneNode(true))) {
                                   BodyProperties = new Draw.BodyProperties(),
                                   ListStyle = new Draw.ListStyle()
                               },
                               ShapeProperties = new ShapeProperties()
                           }).ToList();

    clonedSlide.CommonSlideData = new CommonSlideData
    {
        ShapeTree = new ShapeTree(placeholderShapes) {
            GroupShapeProperties = (GroupShapeProperties)masterShapeTree.GroupShapeProperties.CloneNode(true),
            NonVisualGroupShapeProperties = (NonVisualGroupShapeProperties)masterShapeTree.NonVisualGroupShapeProperties.CloneNode(true)
        }
    };

    SlideIdList slideIdList = presentationPart.Presentation.SlideIdList;

    // Find the highest slide ID in the current list.
    uint maxSlideId = slideIdList.Max(c=>(uint?)((SlideId)c).Id) ?? 256;

    // Insert the new slide into the slide list after the previous slide.
    slideIdList.Append(new SlideId() {
        Id = ++maxSlideId,
        RelationshipId = presentationPart.GetIdOfPart(clonedSlidePart)
    });
    //presentationPart.Presentation.Save();

    return clonedSlidePart;
}

//helper method used above in separate static class
public static IEnumerable<T> ChildElements<T>(this OpenXmlElement el) where T: OpenXmlElement
{
    if (el.HasChildren)
    {
        var child = el.GetFirstChild<T>();
        while (child != null)
        {
            yield return child;
            child = child.NextSibling<T>();
        }
    }
}

the out parameter placeholderShapes is assuming after creating a new slide based on a master template that the developer will wish to alter some of the placeholder content



来源:https://stackoverflow.com/questions/32076114/c-sharp-openxml-sdk-2-5-insert-new-slide-from-slide-masters-with-the-layout

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!