Управление фигурами презентации в .NET

Обзор

Aspose.Slides for .NET представляет объекты на слайде как упорядоченную IShapeCollection. Коллекция является одновременно местом, где вы находите и изменяете объекты, и источником их порядка наложения: индекс 0 — самый задний объект, а последний индекс — самый передний объект.

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

Идентификация и поиск фигур

Индексы коллекции удобны при обработке известного файла, но они не являются стабильными идентификаторами. Добавление, удаление или переупорядочивание фигур может изменить их индекс. Выберите идентификатор в зависимости от того, как презентация создаётся и поддерживается:

  • Name полезно для шаблонов, контролируемых разработчиком, и легко просматривается в панели «Выбор» PowerPoint. Имена можно редактировать, но они не гарантировано уникальны, поэтому при зависимости кода от них следует установить конвенцию именования.
  • AlternativeText удобно использовать, когда описание доступности или тег, добавленный автором, уже идентифицируют фигуру. Оно видно пользователям, может быть локализовано или переписано для доступности и не гарантировано уникально. Не используйте осмысленный текст доступности в качестве ключа базы данных без явного согласования.
  • OfficeInteropShapeId — идентификатор только для чтения, уникальный в пределах слайда и соответствующий идентификатору формы, используемому в PowerPoint‑interop. Применяйте его при интеграции с PowerPoint или когда нужен однозначный ориентир в течение жизни формы. Клонированная или вновь созданная форма — другая форма и получает собственный ID.

Связанное свойство UniqueId имеет область действия презентации, но предназначено для надстроек и может быть переопределено. Не следует рассматривать его как постоянный внешний ключ. Если требуется долговременная идентичность, храните сопоставление во внешних данных приложения и проверяйте, что ожидаемая фигура всё ещё существует.

Для практического примера чтения и обновления как заголовка альтернативного текста, так и описания см. раздел Manage Alternative Text Titles and Descriptions. Используйте альтернативный текст для объяснения визуального содержания читателям и держите его отдельно от имён фигур, которыми код ищет объекты.

Следующий пример ищет по Name с ординарным сравнением и выводит интероперационный ID, ограниченный слайдом. Когда шаблон не содержит ожидаемую фигуру, код сообщает об этом результате вместо продолжения работы с неверным объектом.

using System;
using Aspose.Slides;

using var presentation = new Presentation("input.pptx");
var slide = presentation.Slides[0];

IShape? targetShape = null;
foreach (var shape in slide.Shapes)
{
    if (string.Equals(shape.Name, "RevenueChart", StringComparison.Ordinal))
    {
        targetShape = shape;
        break;
    }
}

if (targetShape is null)
{
    Console.WriteLine("The shape 'RevenueChart' was not found on slide 1.");
}
else
{
    Console.WriteLine($"Found {targetShape.Name}; interop ID: {targetShape.OfficeInteropShapeId}");
}

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

using System;
using Aspose.Slides;
using Aspose.Slides.Export;

using var presentation = new Presentation("input.pptx");
var slide = presentation.Slides[0];

IShape? candidate = null;
foreach (var shape in slide.Shapes)
{
    if (string.Equals(shape.Name, "StatusLabel", StringComparison.Ordinal))
    {
        candidate = shape;
        break;
    }
}

if (candidate is IAutoShape autoShape)
{
    autoShape.TextFrame.Text = "Approved";
    autoShape.AlternativeText = "Approval status: approved";
    presentation.Save("identified-shape.pptx", SaveFormat.Pptx);
}
else
{
    Console.WriteLine("'StatusLabel' is missing or is not an AutoShape.");
}

Идентификация и изменение предустановленных регулировок фигур

Фигуры с предустановленной геометрией могут иметь точки регулировки, управляющие, например, размером углов, пропорциями стрелок или углами дуг. Доступ к ним осуществляется через только‑для‑чтения коллекцию IGeometryShape.Adjustments. Коллекцию предоставляет сама фигура, но каждый IAdjustValue содержит значение, которое можно изменить.

Не опирайтесь только на фиксированный индекс коллекции. Перебирайте регулировки и проверяйте только‑для‑чтения свойство Type, значение ShapeAdjustmentType которого описывает, что именно регулирует данная точка. Свойство только‑для‑чтения Name предоставляет дополнительную информацию об идентификации и особенно полезно, когда предустановка содержит более одной регулировки с одинаковым семантическим типом.

Используйте свойство значения, соответствующее смыслу регулировки:

Тип регулировки Назначение Значение для изменения
CornerSize Размер скруглённых углов RawValue
ArrowTailThickness Толщина хвоста стрелки RawValue
ArrowheadLength Длина острия стрелки RawValue
ArrowheadWidth Ширина острия стрелки RawValue
StartAngle Начальный угол сектора или дуги AngleValue
EndAngle Конечный угол сектора или дуги AngleValue

Type и Name изменить нельзя. RawValue — целое число, доступное для чтения и записи в родных единицах геометрии предустановки, а AngleValue — угол в градусах, также доступный для чтения и записи. Количество, порядок, смысл и допустимый диапазон регулировок зависят от предустановленного ShapeType. Значение, корректное для одной предустановки, может быть некорректным или давать иной эффект для другой.

Когда Type равен ShapeAdjustmentType.Custom, API не распознаёт стандартный семантический смысл. Проверьте Name, тип предустановки и текущее значение и оставьте регулировку без изменений, если ожидаемый смысл и диапазон неизвестны. Даже для распознанных типов проверяйте, не встречается ли тот же тип более одного раза, прежде чем выбирать значение. Статья Connector демонстрирует подобную ситуацию с регулировкой изгиба соединителей.

Ниже приведён полный пример, создающий стандартные и изменённые варианты трёх предустановленных фигур. Он перебирает каждую регулировку, выводит её Name и Type, изменяет размеры через RawValue, изменяет углы через AngleValue и сохраняет результат. Левая колонка сохраняет исходную геометрию; правая — отрегулированный скруглённый прямоугольник, четырёхстороннюю стрелку и сектор.

using System;
using Aspose.Slides;
using Aspose.Slides.Export;

using var presentation = new Presentation();
var slide = presentation.Slides[0];

// Добавляет заголовки для колонок фигур по умолчанию и изменённых.
var defaultColumnLabel = slide.Shapes.AddAutoShape(ShapeType.Rectangle, 40, 20, 250, 30);
defaultColumnLabel.TextFrame.Text = "Default preset geometry";
var adjustedColumnLabel = slide.Shapes.AddAutoShape(ShapeType.Rectangle, 390, 20, 250, 30);
adjustedColumnLabel.TextFrame.Text = "Modified adjustment values";

slide.Shapes.AddAutoShape(ShapeType.RoundCornerRectangle, 80, 70, 160, 70);
var modifiedRoundedRectangle = slide.Shapes.AddAutoShape(ShapeType.RoundCornerRectangle, 430, 70, 160, 70);
modifiedRoundedRectangle.Name = "ModifiedRoundedRectangle";

slide.Shapes.AddAutoShape(ShapeType.QuadArrow, 80, 180, 160, 110);
var modifiedArrow = slide.Shapes.AddAutoShape(ShapeType.QuadArrow, 430, 180, 160, 110);
modifiedArrow.Name = "ModifiedQuadArrow";

slide.Shapes.AddAutoShape(ShapeType.Pie, 95, 330, 130, 130);
var modifiedPie = slide.Shapes.AddAutoShape(ShapeType.Pie, 445, 330, 130, 130);
modifiedPie.Name = "ModifiedPie";

var shapesToAdjust = new IGeometryShape[]
{
    modifiedRoundedRectangle,
    modifiedArrow,
    modifiedPie
};

foreach (var shape in shapesToAdjust)
{
    for (var adjustmentIndex = 0; adjustmentIndex < shape.Adjustments.Count; adjustmentIndex++)
    {
        var adjustment = shape.Adjustments[adjustmentIndex];
        Console.WriteLine($"{shape.Name} / {adjustment.Name}: {adjustment.Type}");

        switch (adjustment.Type)
        {
            case ShapeAdjustmentType.CornerSize:
                adjustment.RawValue = 5000;
                break;
            case ShapeAdjustmentType.ArrowTailThickness:
                adjustment.RawValue = 25000;
                break;
            case ShapeAdjustmentType.ArrowheadLength:
                adjustment.RawValue = 30000;
                break;
            case ShapeAdjustmentType.ArrowheadWidth:
                adjustment.RawValue = 40000;
                break;
            case ShapeAdjustmentType.StartAngle:
                adjustment.AngleValue = 30;
                break;
            case ShapeAdjustmentType.EndAngle:
                adjustment.AngleValue = 300;
                break;
            case ShapeAdjustmentType.Custom:
                Console.WriteLine($"Custom adjustment '{adjustment.Name}' was not changed.");
                break;
        }
    }
}

presentation.Save("preset-shape-adjustments.pptx", SaveFormat.Pptx);

Проверка семантического типа перед изменением значения делает код явным в отношении намерения и предотвращает предположение, что определённый индекс коллекции имеет одинаковый смысл в разных предустановленных фигурах.

Модификация коллекции фигур

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

Клонирование фигуры

AddClone создаёт независимую копию и добавляет её в целевую коллекцию. InsertClone также создаёт копию, но помещает её по указанному индексу Z‑порядка. Перегрузки, принимающие координаты, перемещают клон без изменения размеров; перегрузки с шириной и высотой могут также изменять размер.

Пример создаёт целевой слайд, клонирует помеченный прямоугольник спереди и вставляет второй клон сзади. Изменения любого из клонов не влияют на исходную фигуру.

using System;
using Aspose.Slides;
using Aspose.Slides.Export;

using var presentation = new Presentation();
var sourceSlide = presentation.Slides[0];
var sourceShape = sourceSlide.Shapes.AddAutoShape(ShapeType.Rectangle, 40, 40, 180, 60);
sourceShape.Name = "SourceLabel";
sourceShape.TextFrame.Text = "Source";

var blankLayout = presentation.Masters[0].LayoutSlides.GetByType(SlideLayoutType.Blank);
var destinationSlide = presentation.Slides.AddEmptySlide(blankLayout);

var frontCloneShape = destinationSlide.Shapes.AddClone(sourceShape, 80, 80);
frontCloneShape.Name = "FrontClone";
if (frontCloneShape is IAutoShape frontClone)
{
    frontClone.TextFrame.Text = "Front clone";
}
else
{
    Console.WriteLine("The front clone is not an AutoShape; its text was not changed.");
}

var backCloneShape = destinationSlide.Shapes.InsertClone(0, sourceShape, 80, 180);
backCloneShape.Name = "BackClone";
if (backCloneShape is IAutoShape backClone)
{
    backClone.TextFrame.Text = "Back clone";
}
else
{
    Console.WriteLine("The back clone is not an AutoShape; its text was not changed.");
}

presentation.Save("cloned-shapes.pptx", SaveFormat.Pptx);

Клонирование копирует содержимое и форматирование фигуры, включая её имя и альтернативный текст. Присвойте новые логические идентификаторы клону, если эти значения должны оставаться уникальными. Ресурсы, используемые сложными фигурами, обрабатываются презентацией, но клон остаётся новым элементом коллекции с новой идентичностью фигуры.

Удаление фигур

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

Этот пример удаляет каждую фигуру с указанным именем. Он читает slide.Shapes[i], а не фиксированный элемент коллекции, и не приводит тип фигуры без необходимости.

using System;
using Aspose.Slides;
using Aspose.Slides.Export;

using var presentation = new Presentation();
var slide = presentation.Slides[0];

var keepShape = slide.Shapes.AddAutoShape(ShapeType.Rectangle, 40, 40, 140, 60);
keepShape.Name = "Keep";

var firstTemporaryShape = slide.Shapes.AddAutoShape(ShapeType.Ellipse, 220, 40, 80, 80);
firstTemporaryShape.Name = "Temporary";

var secondTemporaryShape = slide.Shapes.AddAutoShape(ShapeType.Triangle, 340, 40, 100, 80);
secondTemporaryShape.Name = "Temporary";

for (var i = slide.Shapes.Count - 1; i >= 0; i--)
{
    var shape = slide.Shapes[i];
    if (string.Equals(shape.Name, "Temporary", StringComparison.Ordinal))
    {
        slide.Shapes.Remove(shape);
    }
}

presentation.Save("removed-shapes.pptx", SaveFormat.Pptx);

После удаления количество фигур и индексы последующих фигур изменяются. Ссылки на не затронутые фигуры остаются надёжнее, чем сохранённые индексы. Также учитывайте соединители, анимацию и другие возможности презентации, которые могут ссылаться на удалённый объект; удаление видимой фигуры может изменить не только внешний вид слайда.

Сокрытие фигуры

Установка Hidden в true оставляет фигуру в коллекции, но препятствует её появлению в обычной демонстрации. Её индекс, форматирование и содержимое остаются доступными коду, поэтому скрытие уместно для опциональных элементов, которые могут быть восстановлены позже.

using System;
using Aspose.Slides;
using Aspose.Slides.Export;

using var presentation = new Presentation();
var slide = presentation.Slides[0];

var visibleShape = slide.Shapes.AddAutoShape(ShapeType.Rectangle, 40, 40, 160, 60);
visibleShape.Name = "VisibleLabel";

var optionalShape = slide.Shapes.AddAutoShape(ShapeType.Moon, 240, 40, 100, 100);
optionalShape.Name = "OptionalDecoration";

foreach (var shape in slide.Shapes)
{
    if (string.Equals(shape.Name, "OptionalDecoration", StringComparison.Ordinal))
    {
        shape.Hidden = true;
    }
}

presentation.Save("hidden-shape.pptx", SaveFormat.Pptx);

Сокрытие — не удаление и не средство защиты. Объект всё ещё может быть найден и сделан видимым пользователем или кодом и остаётся частью файла презентации.

Изменение Z‑порядка

Перекрывающиеся фигуры отрисовываются в порядке коллекции. Reorder перемещает существующую фигуру к целевому индексу без её клонирования. Индекс 0 — задний; Count - 1 — передний.

using System.Drawing;
using Aspose.Slides;
using Aspose.Slides.Export;

using var presentation = new Presentation();
var slide = presentation.Slides[0];

var blueRectangle = slide.Shapes.AddAutoShape(ShapeType.Rectangle, 100, 100, 220, 120);
blueRectangle.Name = "BlueRectangle";
blueRectangle.FillFormat.FillType = FillType.Solid;
blueRectangle.FillFormat.SolidFillColor.Color = Color.SteelBlue;

var orangeEllipse = slide.Shapes.AddAutoShape(ShapeType.Ellipse, 180, 140, 220, 120);
orangeEllipse.Name = "OrangeEllipse";
orangeEllipse.FillFormat.FillType = FillType.Solid;
orangeEllipse.FillFormat.SolidFillColor.Color = Color.Orange;

slide.Shapes.Reorder(slide.Shapes.Count - 1, blueRectangle);
presentation.Save("reordered-shapes.pptx", SaveFormat.Pptx);

Прямоугольник создаётся первым и изначально находится позади эллипса. Перемещение его к конечному индексу приводит к размещению спереди. Завершайте настройку Z‑порядка после добавления или клонирования всех связанных фигур, поскольку эти операции добавляют новые элементы коллекции и могут изменить предполагаемую стаку.

Проверка фигур на макетных слайдах

Обычные слайды, макетные слайды и слайды‑шаблоны имеют отдельные коллекции фигур. Фигура в коллекции макета — не тот же объект, что аналогичная фигура на обычном слайде. Проводите проверку фигур макета, когда необходимо понять или изменить форматирование, предоставляемое макетом.

Следующий пример читает у каждой фигуры макета свойства FillFormat и LineFormat без предположения, что каждая фигура является AutoShape.

using System;
using Aspose.Slides;

using var presentation = new Presentation("input.pptx");

foreach (var layoutSlide in presentation.LayoutSlides)
{
    foreach (var shape in layoutSlide.Shapes)
    {
        var fillType = shape.FillFormat.FillType;
        var lineWidth = shape.LineFormat.Width;
        Console.WriteLine($"{layoutSlide.Name} / {shape.Name}: fill={fillType}, line width={lineWidth}");
    }
}

Изменение макета может затронуть несколько слайдов, использующих его. Прежде чем менять фигуру макета, определите, наследует ли обычный слайд объект или содержит локальное переопределение, и протестируйте каждый слайд, использующий этот макет.

Экспорт фигуры в SVG

WriteAsSvg записывает отрисованное содержимое одной фигуры в поток. Результат содержит только эту фигуру, а не весь фон слайда или соседние фигуры.

using System;
using System.IO;
using Aspose.Slides;

using var presentation = new Presentation("input.pptx");
var slide = presentation.Slides[0];

if (slide.Shapes.Count == 0)
{
    Console.WriteLine("Slide 1 does not contain a shape to export.");
}
else
{
    var shape = slide.Shapes[0];
    using var svgStream = File.Create("shape.svg");
    shape.WriteAsSvg(svgStream);
}

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

Выравнивание фигур

Перегрузки SlideUtil.AlignShapes выравнивают либо все фигуры, либо выбранные индексы коллекции. ShapesAlignmentType задаёт ребро, центральную линию или режим распределения. Установите alignToSlide в true, чтобы использовать границы слайда; установите в false, чтобы выравнивать выбранные фигуры относительно друг друга.

В этом примере три фигуры выравниваются по верхнему краю слайда. Возвращённые ссылки на фигуры преобразуются в их текущие индексы непосредственно перед выравниванием.

using Aspose.Slides;
using Aspose.Slides.Export;
using Aspose.Slides.Util;

using var presentation = new Presentation();
var slide = presentation.Slides[0];

var firstShape = slide.Shapes.AddAutoShape(ShapeType.Rectangle, 60, 80, 120, 50);
var secondShape = slide.Shapes.AddAutoShape(ShapeType.Ellipse, 240, 160, 120, 50);
var thirdShape = slide.Shapes.AddAutoShape(ShapeType.Triangle, 420, 240, 120, 50);
firstShape.Name = "FirstAlignedShape";
secondShape.Name = "SecondAlignedShape";
thirdShape.Name = "ThirdAlignedShape";

var shapeIndexes = new[]
{
    slide.Shapes.IndexOf(firstShape),
    slide.Shapes.IndexOf(secondShape),
    slide.Shapes.IndexOf(thirdShape)
};

SlideUtil.AlignShapes(ShapesAlignmentType.AlignTop, true, slide, shapeIndexes);
presentation.Save("aligned-shapes.pptx", SaveFormat.Pptx);

Выравнивание меняет позиции, а не Z‑порядок. Относительное выравнивание обычно требует как минимум две фигуры, а горизонтальное или вертикальное распределение — достаточного количества фигур для определения промежутков. Пересчитайте индексы, если вы изменяете коллекцию перед вызовом метода.

Отражение фигуры

Класс ShapeFrame хранит позицию, размер, параметры горизонтального и вертикального отражения и поворот. Его свойства FlipH и FlipV используют NullableBool: True включает отражение, False отключает, а NotDefined сохраняет неуказанное/по‑умолчанию состояние.

Исходная презентация ниже содержит одну неотражённую фигуру.

The shape before flipping

Пример сохраняет все остальные значения кадра и заменяет только два параметра отражения. Это важно, поскольку присвоение нового Frame заменяет весь кадр.

using System;
using Aspose.Slides;
using Aspose.Slides.Export;

using var presentation = new Presentation("sample.pptx");
var shape = presentation.Slides[0].Shapes[0];
var frame = shape.Frame;

Console.WriteLine($"Horizontal flip before change: {frame.FlipH}");
Console.WriteLine($"Vertical flip before change: {frame.FlipV}");

shape.Frame = new ShapeFrame(
    frame.X, frame.Y, frame.Width, frame.Height,
    NullableBool.True, NullableBool.True, frame.Rotation);

presentation.Save("flipped-shape.pptx", SaveFormat.Pptx);

Сохранённая фигура зеркально отражена по горизонтали и вертикали, при этом сохраняет позицию, размер и поворот.

The shape after flipping

FAQ

Следует ли использовать индекс коллекции в качестве идентификатора фигуры?

Только для короткоживущей обработки, когда коллекция не изменится до использования индекса. Для шаблонов, создаваемых разработчиком, предпочтительнее проверенный Name или конвенция AlternativeText; для работы с интеропом в пределах слайда — OfficeInteropShapeId.

Удаляется ли скрытая фигура из Z‑порядка?

Нет. Скрытая фигура остаётся в коллекции на том же индексе. Её можно найти, переупорядочить, отредактировать или снова сделать видимой.

Почему клон фигуры оказался перед другой фигурой?

AddClone добавляет клон в конец коллекции, а конец — передняя часть Z‑порядка. Используйте InsertClone, чтобы задать начальный индекс, либо Reorder после добавления всех фигур.

Можно ли использовать фиксированный индекс для идентификации регулировки предустановленной фигуры?

Только после подтверждения точной предустановки и расположения коллекции. Предпочтительнее перебрать IGeometryShape.Adjustments и проверить IAdjustValue.Type; при появлении более одного одинакового семантического типа используйте IAdjustValue.Name как дополнительную информацию.