I am currently using the Map Sample from Microsoft. This sample creates clusters of pins on the map to save space.
I am finding it difficult to be able to select a POI that is on the map and get the properties of the POI.
There are over 1000, POIs so I need clustering, but the sample is not clear on how to select the POI in question.
Code is below:
private async Task LoadPlaceInfoAsync()
{
Uri dataUri = new Uri("ms-appx:///places.txt");
StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(dataUri);
IList<string> lines = await FileIO.ReadLinesAsync(file);
// In the places.txt file, each place is represented by three lines:
// Place name, latitude, and longitude.
for (int i = 0; i < lines.Count; i += 3)
{
PlaceInfo place = new PlaceInfo
{
Name = lines[i],
Location = new PlaceLocation(double.Parse(lines[i + 1]), double.Parse(lines[i + 2]))
};
places.Add(place);
}
}
private void refreshMapIcons()
{
// Erase the old map icons.
myMap.MapElements.Clear();
// Create an icon for each cluster.
foreach (var cluster in GetClustersForZoomLevel(previousZoomLevel))
{
MapIcon mapIcon = new MapIcon
{
Location = new Geopoint(cluster.Location.Geoposition),
CollisionBehaviorDesired = MapElementCollisionBehavior.RemainVisible,
};
if (cluster.Places.Count > 1)
{
// The cluster represents more than one place. Use a custom marker that shows
// how many places are represented by this cluster, and place the marker
// centered at the cluster.
mapIcon.Image = numberIconReferences[Math.Min(cluster.Places.Count, 9) - 2];
mapIcon.NormalizedAnchorPoint = new Point(0.5, 0.5);
}
else
{
// The cluster represents a single place. Label the cluster with the place name.
mapIcon.Title = cluster.Places[0].Name;
}
myMap.MapElements.Add(mapIcon);
}
}
Related
I need to write AutoCAD plugin to display the area of the object.
Below is my code.
It works fine, but test is static. I need to keep tracking the area of the circle cir.Area.ToString();.
Currently, If I change the size of the circle latter on, the text does not change anymore.
For example, the area of my circle is 10. I run code, it displays 10. But if I change the radius of circle, the text remains 10.
How can I make it working.
[CommandMethod("displayarea")]
public static void Displayarea()
{
var doc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
var db = doc.Database;
var ed = doc.Editor;
var filter = new SelectionFilter(new[] { new TypedValue(0, "Circle") });
var selection = ed.GetSelection(filter);
if (selection.Status != PromptStatus.OK)
return;
using (var tr = db.TransactionManager.StartTransaction())
{
var curSpace = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
foreach (var id in selection.Value.GetObjectIds())
{
var ids = new ObjectIdCollection(new[] { id });
Circle cir = (Circle)tr.GetObject(id, OpenMode.ForRead) as Circle;
var _centerPosition = cir.Center;
using (DBText acText = new DBText())
{
acText.Position = _centerPosition;
acText.TextString = cir.Area.ToString();
acText.Height = 0.5;
curSpace.AppendEntity(acText);
tr.AddNewlyCreatedDBObject(acText, true);
}
}
tr.Commit();
}
}
Also you can use oEntity.Modified += OEntity_Modified;
Find this: Find which properties changed on modified event
You need to use fields.
Find this:
https://www.keanw.com/2007/07/accessing-the-a.html
It works by replacing
cir.Area.ToString();
to
string circarea = "%<\\AcObjProp Object(%<\\_ObjId "
+ CircleId
+ ">%).Area \\f \"%lu2\">%";
I want to be able to fetch .csv files from a folder and plot them up in a chart.
Currently, I'm just saving the files and displaying an individual curve like this:
RunTest function:
public List<Tuple<double,double>> runTest()
{
_dpg = new Root(this, "english", false);
_dpg.Init();
var filename = "Dpg10Export_" + DateTime.Now.ToString("yyyyMMdd_HHmm") + ".csv";
List<Tuple<double, double>> results = new List<Tuple<double, double>>();
var measurement = new Measurement();
var resultCode = RunMeasurement(60, 1000, 2200, ref measurement, null /* TODO: ref ProgressBar? */);
using (var fileStream = new StreamWriter(filename))
{
var count = measurement.I_inc_Values.Count;
for (var i = 0; i < count; i++)
{
var item = measurement.I_inc_Values[i + 1];
var current = (float)Convert.ToDouble(item);
item = measurement.LI_inc_Values[i + 1];
var inductance = (float)Convert.ToDouble(item);
var line = current.ToString() + ";" + inductance.ToString() + ";";
fileStream.WriteLine(line);
currentList.Add(current);
inductanceList.Add(inductance);
results.Add(new Tuple<double, double>(current,inductance));
if (i == 0 || (i + 1) % 32 == 0)
{
Console.WriteLine((i + 1) + ": " + line);
}
}
}
return results;
}
This code produces a csv-file that looks something like this:
0,22 | 0,44
0,32 | 0,54
0,44 | 0,65
And those values produce a curve that looks like this:
When you click on the "get Waveform" button, the curve above is generated. However, I want to display all the curves that has been generated, and a trendline as well. How would I achieve this?
void BindData(List<Tuple<double,double>> results)
{
chart.Series.Clear();
var series1 = new System.Windows.Forms.DataVisualization.Charting.Series
{
Name = "curr/induc",
Color = System.Drawing.Color.Green,
IsVisibleInLegend = true,
IsXValueIndexed = true,
ChartType = SeriesChartType.Line
};
foreach (var i in results)
{
series1.Points.AddXY(i.Item2,i.Item1);
}
chart.Invalidate();
chart.Series.Add(series1);
}
private void getWaveformBtn_Click(object sender, EventArgs e)
{
Dpg10Client.Dpg10Settings settings = new Dpg10Client.Dpg10Settings();
Dpg10Instrument hej = new Dpg10Instrument(settings);
List<Tuple<double,double>> results = hej.runTest();
double current, inductance;
foreach(var i in results)
{
current = i.Item1;
inductance = i.Item2;
textBoxWaveformInput.Text += current.ToString() + inductance.ToString();
}
BindData(results);
}
TL;DR
Parse the information from the CSV files to generate curves, and create a trendline based on those files.
Marked as duplicate: That answer is regarding a straight line, these values can fluctuate in a curve.
There are many ways to solve most of the various problems in your question.
Let me tackle only the one that actually has to do with calculating an average from multiple series, i.e. will not deal with creating a Series with data from a CSV file.
There is a built-in class that can do all sorts of advanced math, both financial and statistical, but I didn't find one that will help in creating an average line/curve over more than one series.
So let's do it ourselves..
The first issue is that to calculate averages we need not just data but the data, that is their x-values, must be grouped into 'bins' from which we want to get the averages.
Unless the data already are grouped like that, e.g. because they have one value per series per day, we need to create such groups.
Let's first collect all the points from all series we want to handle; you may need to adapt the loop to include just your set of series..:
var allPoints = new List <DataPoint>();
for (int s = 0; s < 3; s++) // I know I have created these three series
{
Series ser = chartGraphic.Series["S" + (s+1)];
allPoints.AddRange(ser.Points);
}
Next we need to decide on a bin range/size, that is a value that determines which x-values shall fall into the same bin/group. I chose to have 10 bins.
So we best get the total range of the x-values and divide by the number of bins we want..:
double xmax = allPoints.Max(x => x.XValue);
double xmin = allPoints.Min(x => x.XValue);
int bins = 10;
double groupSize = (xmax - xmin) / (bins - 1);
Next we do the actual math; for this we order our points, group them and select the average for each grouped set..:
var grouped = allPoints
.OrderBy(x => x.XValue)
.GroupBy(x => groupSize * (int)(x.XValue /groupSize ))
.Select(x => new { xval = x.Key, yavg = x.Average(y => y.YValues[0]) })
.ToList();
Now we can add them to a new series to display the averages in the bins:
foreach (var kv in grouped) avgSeries.Points.AddXY(kv.xval + groupSize/2f, kv.yavg);
I center the average point in the bins.
Here is an example:
A few notes on the example:
The average line doesn't show a real 'trend' because my data are pretty much random.
I have added Markers to all series to make the DataPoints stand out from the lines. Here it is how I did it for the averages series:
avgSeries.MarkerStyle = MarkerStyle.Cross;
avgSeries.MarkerSize = 7;
avgSeries.BorderWidth = 2;
I have added a StripLine to show the bins. Here is how:
StripLine sl = new StripLine();
sl.BackColor = Color.FromArgb(44,155,155,222);
sl.StripWidth = groupSize;
sl.Interval = groupSize * 2;
sl.IntervalOffset = chartGraphic.ChartAreas[0].AxisX.IntervalOffset;
chartGraphic.ChartAreas[0].AxisX.StripLines.Add(sl);
I have also set one point to have a really low value of -300 to demonstrate how this will pull down the average in its bin. To keep the chart still nicely centered on the normal range of data I have added a ScaleBreakStyle to the y-axis:
chartGraphic.ChartAreas[0].AxisY.ScaleBreakStyle.BreakLineStyle = BreakLineStyle.Wave;
chartGraphic.ChartAreas[0].AxisY.ScaleBreakStyle.Enabled = true;
Let me start by apologizing for the complexity of this post. hopefully I am missing something simple but in order to find out I have to make a lengthy explanation.
I'm building a staff tracking app that allows users to draw polygons on a map. The polygon is used as a zone. If the device location is inside the zone it will set them to one status such as "In" and then set them to another status like "Out" when they leave. I'm using several nugets; SQLite, TK.CustomMap, PopupPlugin, to accomplish as much of this as possible in the Shared Project. The SQLite data model is based on the structure of the remote database for the solution which also has a desktop application, web app, and many other interfaces so that structure must be maintained.
The tables involved in this operation are Zones, ZonePoints and StatusClass. When the Zone is saved the Points of the TK.Polygon are saved into the points table. The Statuses assigned to the in and out status of the Zone are assigned to the zone as is the zone name.
The process works this way - first the user clicks on a button to add the zone. This creates a new zone, saves it to the database and gets its id from the table. For now, the ZoneSys (Primary Key in the remote db) is also set to the id although when the API is ready this will be Auto Incremented in the remote db. Here is that command and the objects it references. I did not include the database method definitions given that they work on the first time through, but if anyone thinks they will help solve this riddle please let me know -
public static List<Position> ActiveZonePositions = new List<Position>();
public static int ActiveZoneID;
public static Zone ActiveZone;
public static int PointCount;
public static bool NewZoneOpen = false;
public Command<EventArgs> OpenNewZone
{
get
{
return new Command<EventArgs>(async e =>
{
NewZoneOpen = true;
PointCount = 0;
ActiveZonePositions.Clear();
Zone ZoneToAdd = new Zone
{
Contactsys = MobileUser.ContactSys,
ZoneTypeSys = 1,
OrganizationSys = MobileUser.OrganizationSys,
ZoneName = null
};
ActiveZoneID = await AddZoneToDBAsync(ZoneToAdd);
ZoneToAdd.ID = ActiveZoneID;
ZoneToAdd.ZoneSys = ActiveZoneID;
await AddZoneToDBAsync(ZoneToAdd);
ActiveZone = await App.Database.GetZoneAsync(ActiveZoneID);
});
}
}
As the user click points in the map the polygon is drawn using those positions and those positions are also used to create points which are added to a static list. Here is the MapClicked_Command -
public Command<Position> MapClickedCommand
{
get
{
return new Command<Position>(async position =>
{
if (NewZoneOpen)
{
bool isPointInPolygon = IsPointInAnyPolygon(position);
if (isPointInPolygon)
{
var action = await Application.Current.MainPage.DisplayActionSheet(
"Region Collides with Another Region",
"Cancel",
null,
"Try Again",
"Close Zone Editor");
if (action == "Close Zone Editor")
{
await RemoveZoneAsync(ActiveZoneID);
}
if (action == "Try Again")
{
return;
}
}
else if (!isPointInPolygon)
{
ActiveZonePositions.Add(position);
}
if (ActiveZonePositions.Count == 2)
{
ZonePolyLine.LineCoordinates = ActiveZonePositions;
_lines.Remove(ZonePolyLine);
_lines.Add(ZonePolyLine);
}
else if (ActiveZonePositions.Count == 3)
{
ActiveZonePositions.Add(position);
_lines.Remove(ZonePolyLine);
TKPolygon poly = new TKPolygon
{
StrokeColor = System.Drawing.Color.CornflowerBlue,
StrokeWidth = 2f,
Color = System.Drawing.Color.CornflowerBlue
};
foreach (Position pos in ActiveZonePositions)
{
poly.Coordinates.Add(pos);
}
_polygons.Add(poly);
_currentPolygon.Clear();
_currentPolygon.Add(poly);
currentPolygonRendering = true;
}
else if (ActiveZonePositions.Count > 3)
{
ActiveZonePositions.Add(position);
TKPolygon poly = new TKPolygon
{
StrokeColor = System.Drawing.Color.CornflowerBlue,
StrokeWidth = 2f,
Color = System.Drawing.Color.CornflowerBlue
};
foreach (Position pos in ActiveZonePositions)
{
poly.Coordinates.Add(pos);
}
_polygons.Remove(_polygons.Last());
_polygons.Add(poly);
_currentPolygon.Clear();
_currentPolygon.Add(poly);
}
var pin = new TKCustomMapPin
{
Position = new TK.CustomMap.Position(position.Latitude, position.Longitude),
Title = string.Format("Pin {0}, {1}", position.Latitude, position.Longitude),
IsVisible = true,
IsDraggable = true,
ShowCallout = true
};
_pins.Add(pin);
await CreatePointAsync(position);
PointCount++;
}
else if (EditZoneOpen)
{
ActiveZonePositions.Add(position);
var poly = _polygons[0];
poly.Coordinates.Clear();
foreach (Position pos in ActiveZonePositions)
{
poly.Coordinates.Add(pos);
}
_polygons.Remove(_polygons.Last());
_polygons.Add(poly);
_currentPolygon.Clear();
_currentPolygon.Add(poly);
var pin = new TKCustomMapPin
{
Position = new TK.CustomMap.Position(position.Latitude, position.Longitude),
Title = string.Format("Pin {0}, {1}", position.Latitude, position.Longitude),
IsVisible = true,
IsDraggable = true,
ShowCallout = true
};
_pins.Add(pin);
await CreatePointAsync(position);
PointCount++;
}
});
}
}
Here is the CreatePointAsyncMethod -
public async Task CreatePointAsync(TK.CustomMap.Position position)
{
var zone = await RetrieveZoneAsync(ActiveZoneID);
Model.Point PointToAdd = new Model.Point
{
ZoneSys = zone.ZoneSys,
PointName = "",
Latitude = position.Latitude,
Longitude = position.Longitude,
PointOrder = PointCount + 1
};
ActiveZonePoints.Add(PointToAdd);
}
Here is the IsPointInAnyPolygon method that checks against the list of polygons to ensure the point clicked is not inside any of them as well as its supporting methods.
private bool IsPointInAnyPolygon(Position position)
{
bool inBounds = false;
for (var i = 0; i < ZonePolygons.Count(); i++)
foreach (ZonePolygon zpoly in ZonePolygons)
{
TKPolygon tkpoly = zpoly.Zpolygon;
inBounds = IsPointInPolygon(position, tkpoly.Coordinates);
if (inBounds)
{
ActiveZoneID = zpoly.ID;
return inBounds;
}
}
return inBounds;
}
private bool IsPointInPolygon(TK.CustomMap.Position position, List<Position> coords)
{
int intersectCount = 0;
for (int j = 0; j < coords.Count() - 1; j++)
{
if (j+1 >= coords.Count())
{
if (rayCastIntersect(position, coords[j], coords[0]))
{
intersectCount++;
}
} else if (rayCastIntersect(position, coords[j], coords[j + 1]))
{
intersectCount++;
}
}
return ((intersectCount % 2) == 1); // odd = inside, even = outside;
}
private bool rayCastIntersect(TK.CustomMap.Position position, TK.CustomMap.Position vertA, TK.CustomMap.Position vertB)
{
double aY = vertA.Latitude;
double bY = vertB.Latitude;
double aX = vertA.Longitude;
double bX = vertB.Longitude;
double pY = position.Latitude;
double pX = position.Longitude;
if ((aY > pY && bY > pY) | (aY < pY && bY < pY)
| (aX < pX && bX < pX))
{
return false; // a and b can't both be above or below pt.y, and a or
// b must be east of pt.x
}
double m = (aY - bY) / (aX - bX); // Rise over run
double bee = (-aX) * m + aY; // y = mx + b
double x = (pY - bee) / m; // algebra is neat!
return x > pX;
}
Upon clicking the save button a popup opens that allows the user to give the Zone a name, define the statuses it will be assigned and the points are added to the database. There is a ZoneSys column in the Points table which allows the points to be matched to their respective zones when retrieved. This is done withe the UpdateZone command
public Command<EventArgs> UpdateZone
{
get
{
return new Command<EventArgs>(async e =>
{
Zone zone = await App.Database.GetZoneAsync(ActiveZoneID);
zone.ZoneName = ZoneParameters.ZoneName;
zone.StatusSys = ZoneParameters.InStatus.StatusSys;
zone.OutOfZoneStatusSys = ZoneParameters.OutStatus.StatusSys;
await AddZoneToDBAsync(zone);
if (MapPage.SaveZoneInfoPopupPageOpen)
{
SavePointsOnExit();
MapPage.SaveZoneInfoPopupPageOpen = false;
}
});
}
}
The UpdateZone command calls the SavePointsOnExit method
private async void SavePointsOnExit()
{
ActiveZonePoints.OrderBy(o => o.PointOrder);
for (var i = 0; i < ActiveZonePoints.Count(); i++)
{
Model.Point PointToAdd = new Model.Point();
PointToAdd = ActiveZonePoints[i];
ActivePointID = await AddPointToDBAsync(PointToAdd);
PointToAdd.ID = ActivePointID;
PointToAdd.PointSys = ActivePointID;
await AddPointToDBAsync(PointToAdd);
}
try
{
Zone zone = await RetrieveZoneAsync(ActiveZoneID);
}
catch
{
await Application.Current.MainPage.DisplayActionSheet("no zone returned", "database error", "cancel");
}
try
{
Zone zone = await RetrieveZoneAsync(ActiveZoneID);
await CreateZonedPolygonAsync(zone);
}
catch
{
await Application.Current.MainPage.DisplayActionSheet("Could not create ZonePolygon", "object error", "cancel");
}
ActiveZonePoints.Clear();
ActiveZonePositions.Clear();
NewZoneOpen = false;
ClearPins();
PointCount = 0;
PopulatePoints();
}
In addition to saving the points to the db the SaveZonePointsOnExit method also creates the ZonePolygon and adds it to an observable collection using the CreateZonedPolygonAsync method -
private async Task<ZonePolygon> CreateZonedPolygonAsync(Zone zone)
{
int StatusSys = zone.StatusSys;
var status = await App.Database.GetStatusBySysAsync(StatusSys);
int OutStatusSys = zone.OutOfZoneStatusSys;
var outStatus = await App.Database.GetStatusBySysAsync(OutStatusSys);
var points = await App.Database.GetZonePointsAsync(zone.ZoneSys);
ZonePolygon zonePolygon = new ZonePolygon
{
ID = zone.ID
};
TKPolygon poly = new TKPolygon();
foreach (Model.Point point in points)
{
poly.Coordinates.Add(new Position(point.Latitude, point.Longitude));
}
poly.Color = Color.FromHex(status.ColorCode);
poly.StrokeColor = Color.Firebrick;
poly.StrokeWidth = 5f;
_polygons.Add(poly);
ZonePolygons.Add(zonePolygon);
return zonePolygon;
}
So far all of this works to a point. I have been successful in creating the first Polygon. I don't run into a problem until I attempt to create a second Zone. When I click on the AddZone button a second time that works fine but when I click on the map to begin creating the second zone a nullreference exception occurs.
Given that the first zone is created without issue I think the problem must be arising from something that occurs when the IsPointInAnyPolygon method no longer immediately returns false because the ZonePolygons list is no longer empty. So something about the retrieving of zones from the database to check against is the problem or possibly adding coordinates when the TKPolygon is created. I don't know what has a null reference. I would think that since I am creating the Zones directly from the database that all the objects would be saved properly and their previous references wouldn't matter. I'm very stuck on this.
TL;DR there is an issue with either the CreateZonedPolygonAsync Method or the IsPointInAnyPolygon method
I figured this out. I feel rather silly because I have been stuck on this for several hours spread out over a couple of weeks. Kept coming back to it and couldn't figure it out. The issue was that in the CreateZonedPolygonAsync method I never assigned the TKPolygon created via points to the ZonePolygon object being created. So when I tried to reference it it didn't exist. All that existed was the ID. Can't believe I missed this for this long.
Of course now I'm having brand new problems but at least this is fixed.
All I had to do was add zonePolygon.Zpolygon = poly; as shown here and it works now
private async Task<ZonePolygon> CreateZonedPolygonAsync(Zone zone)
{
int StatusSys = zone.StatusSys;
var status = await App.Database.GetStatusBySysAsync(StatusSys);
int OutStatusSys = zone.OutOfZoneStatusSys;
var outStatus = await App.Database.GetStatusBySysAsync(OutStatusSys);
var points = await App.Database.GetZonePointsAsync(zone.ZoneSys);
ZonePolygon zonePolygon = new ZonePolygon
{
ID = zone.ID
};
TKPolygon poly = new TKPolygon();
foreach (Model.Point point in points)
{
poly.Coordinates.Add(new Position(point.Latitude, point.Longitude));
}
poly.Color = Color.FromHex(status.ColorCode);
poly.StrokeColor = Color.Firebrick;
poly.StrokeWidth = 5f;
zonePolygon.Zpolygon = poly;
_polygons.Add(poly);
ZonePolygons.Add(zonePolygon);
return zonePolygon;
}
Does anyone know how to use the visio insertListMember method (below) in c#?
https://msdn.microsoft.com/en-us/library/office/ff768115.aspx
I have tried to execute the method with the following commands but it gives a "Run Time Error- 424 object required"
I have also used the dropIntoList method and it works fine but for specific purposes I need to use the insertListMember method. (to determine the height of the list)
static void Main(string[] args)
{
//create the object that will do the drawing
visioDrawing.VisioDrawer Drawer = new visioDrawing.VisioDrawer();
Drawer.setUpVisio();
Visio.Shape testShape;
Visio.Shape testShape1;
testShape = Drawer.DropShape("abc", "lvl1Box");
testShape1 = Drawer.DropShape("ccc", "Capability");
Drawer.insertListMember(testShape, testShape1, 1);
}
public void insertListMember(Visio.Shape outerlist, Visio.Shape innerShape, int position)
{
ActiveDoc.ExecuteLine(outerlist + ".ContainerProperties.InsertListMember" + innerShape + "," + position);
}
To obtain the shape:
public Visio.Shape DropShape(string rectName, string masterShape)
{
//get the shape to drop from the masters collection
Visio.Master shapetodrop = GetMaster(stencilPath, masterShape);
// drop a shape on the page
Visio.Shape DropShape = acPage.Drop(shapetodrop, 1, 1);
//put name in the shape
Visio.Shape selShape = selectShp(DropShape.ID);
selShape.Text = rectName;
return DropShape;
}
private Visio.Master GetMaster(string stencilName, string mastername)
{
// open the page holding the masters collection so we can use it
MasterDoc = MastersDocuments.OpenEx(stencilName, (short)Visio.VisOpenSaveArgs.visOpenDocked);
// now get a masters collection to use
Masters = MasterDoc.Masters;
return Masters.get_ItemU(mastername);
}
From your code, 'Drawer' looks to be some kind of Visio app wrapper, but essentially InsertListMember allows you to add shapes to a list that already exist on the page. Here's an example of the method and an alternative Page.DropIntoList if you just want to drop directly from the stencil:
void Main()
{
// 'GetRunningVisio' as per
// http://visualsignals.typepad.co.uk/vislog/2015/12/getting-started-with-c-in-linqpad-with-visio.html
// but all you need is a reference to the app
var vApp = MyExtensions.GetRunningVisio();
var vDoc = vApp.Documents.Add("wfdgm_m.vstx");
var vPag = vDoc.Pages[1];
var vCtrlsStencil = vApp.Documents["WFCTRL_M.VSSX"];
var vListMst = vCtrlsStencil?.Masters["List box"];
if (vListMst != null)
{
var vListShp = vPag.Drop(vListMst, 2, 6);
var vListItemMst = vCtrlsStencil.Masters["List box item"];
var insertPosition = vListShp.ContainerProperties.GetListMembers().Length - 1;
//Use InsertListMember method
var firstListItem = vPag.Drop(vListItemMst, 4, 6);
vListShp.ContainerProperties.InsertListMember(firstListItem, insertPosition);
firstListItem.CellsU["FillForegnd"].FormulaU = "3"; //Green
//or use DropIntoList method on Page instead
var secondListItem = vPag.DropIntoList(vListItemMst, vListShp, insertPosition);
secondListItem.CellsU["FillForegnd"].FormulaU = "2"; //Red
}
}
This is using the Wireframe Diagram template (in Visio Professional) and should result in the following:
In case people were wondering I ended up fixing the method up. However I believe that this method is not required if you are using visio Interop assemblies v15. (I am using v14)
public void insertListMember(int outerShpID, int innerShpID, int position)
{
acWindow.DeselectAll();
Visio.Page page = acWindow.Page;
acWindow.Select(page.Shapes.get_ItemFromID(innerShpID), (short)Microsoft.Office.Interop.Visio.VisSelectArgs.visSelect);
Debug.WriteLine("Application.ActivePage.Shapes.ItemFromID(" + outerShpID + ").ContainerProperties.InsertListMember ActiveWindow.Selection," + position);
ActiveDoc.ExecuteLine("Application.ActivePage.Shapes.ItemFromID(" + outerShpID + ").ContainerProperties.InsertListMember ActiveWindow.Selection," + position);
}
I used this 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();
}
}
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
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();
}