I'm trying to iterate over fields located in the header and footer of vendor generated documents using OpenXML in an effort to replace them with the result value stored in the field, then remove the field. Below is the code from the footer for just one of the several fields (imagine 4 or 5 more after this one). I'm limited to .Net 3.5 framework and OpenXML SDK 2.0 due to running this as part of an SSIS script task.
<w:r>
<w:rPr>
<w:rFonts w:ascii="Segoe UI" w:hAnsi="Segoe UI" w:eastAsia="Segoe UI"/>
<w:sz w:val="20"/>
</w:rPr>
<w:fldChar w:fldCharType="begin"/>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:ascii="Segoe UI" w:hAnsi="Segoe UI" w:eastAsia="Segoe UI"/>
<w:sz w:val="20"/>
</w:rPr>
<w:instrText xml:space="preserve"> REF NG_MACRO "STANDARD" "patient_lname" </w:instrText>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:ascii="Segoe UI" w:hAnsi="Segoe UI" w:eastAsia="Segoe UI"/>
<w:sz w:val="20"/>
</w:rPr>
<w:fldChar w:fldCharType="separate"/>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:ascii="Segoe UI" w:hAnsi="Segoe UI" w:eastAsia="Segoe UI"/>
<w:sz w:val="20"/>
</w:rPr>
<w:t xml:space="preserve">Test</w:t>
</w:r>
I've tried multiple approaches I've found through the past 3 weeks of research, but all seem to fail or only affect the first field, but not the rest.
Below is an example of what I've tried to do that seemed to work the best, but again, it's only finding the first field and ignoring the rest. Note: there is a set of page numbering fields after these vendor fields akin to / that I do not want to change.
using (WordprocessingDocument document = WordprocessingDocument.Open("Plan.doc", true))
{
MainDocumentPart main = document.MainDocumentPart;
foreach (FooterPart foot in main.FooterParts)
{
foreach(var fld in foot.RootElement.Descendants<FieldCode>())
{
if (fld != null && fld.InnerText.Contains("REF NG_MACRO"))
{
Run rFldCode = (Run)fld.Parent;
// Get the three (3) other Runs that make up our merge field
Run rBegin = rFldCode.PreviousSibling<Run>();
Run rSep = rFldCode.NextSibling<Run>();
Run rText = rSep.NextSibling<Run>();
Run rEnd = rText.NextSibling<Run>();
// Get the Run that holds the Text element for our merge field
// Get the Text element and replace the text content
Text t = rText.GetFirstChild<Text>();
//t.Text = replacementText;
// Remove all the four (4) Runs for our merge field
rFldCode.Remove();
rBegin.Remove();
rSep.Remove();
rEnd.Remove();
}
}
foot.Footer.Save();
}
document.MainDocumentPart.Document.Save();
document.Close();
}
I appreciate any insight and thoughts that anyone can offer on what I'm missing, a better way to achieve this with OpenXML, etc.
Try this. It works for me.
using (WordprocessingDocument wordDocument = WordprocessingDocument.Open("Plan.doc", true))
{
if (null != wordDocument)
{
const string FieldDelimeter = #" MERGEFIELD ";
List<string> listeChamps = new List<string>();
foreach (FooterPart footer in wordDocument.MainDocumentPart.FooterParts)
{
foreach(var field in footer.RootElement.Descendants<FieldCode>())
{
int fieldNameStart = field.Text.LastIndexOf(FieldDelimeter, System.StringComparison.Ordinal);
if (fieldNameStart >= 0)
{
var fieldName = field.Text.Substring(fieldNameStart + FieldDelimeter.Length).Trim();
Run xxxfield = (Run)field.Parent;
Run rBegin = xxxfield.PreviousSibling<Run>();
Run rSep = xxxfield.NextSibling<Run>();
Run rText = rSep.NextSibling<Run>();
Run rEnd = rText.NextSibling<Run>();
if (null != xxxfield)
{
Text t = rText.GetFirstChild<Text>();
t.Text = replacementText;
}
}
}
}
}
}
To get the document, headers and footers FieldCode and then replace the text, you should do this in 2 steps:
Get all the merge fields
Execute the replace text for each fields
Here is how you can get ALL FieldCode:
public IEnumerable<FieldCode> GetMergeFields(WordprocessingDocument doc)
{
var mergeFields = new List<FieldCode>();
if (doc == null) return mergeFields;
mergeFields.AddRange(doc.MainDocumentPart.RootElement.Descendants<FieldCode>());
foreach (var header in doc.MainDocumentPart.HeaderParts)
{
mergeFields.AddRange(header.RootElement.Descendants<FieldCode>());
}
foreach (var footer in doc.MainDocumentPart.FooterParts)
{
mergeFields.AddRange(footer.RootElement.Descendants<FieldCode>());
}
return mergeFields;
}
The documentation on doing this in general is terrible and the answers around the web are incomplete so I'm posting this here. In the end I used code from both of the older answers posted on this question and I had the added requirement of needing to also sub-in images that were base64 encoded.
The use-case I was dealing with specifically were custom document properties, and I had cases with weirdness like when it was removing the set of runs that make up the FieldCode. The code answered above was inadequate because there were still runs left-over. I've fixed that in this case by specifically checking for the end field character.
It uses SkiaSharp for the image loading from a base64 data URL and I'm setting some caps on the image size but if you don't need images it can be removed fairly easily.
There are a couple extra classes and functions I'm using but it's all things like retrieving the replacement values and reporting the finished result that are usage-specific.
As a reference for the EMU sizing this was the best reference I found: http://polymathprogrammer.com/2009/10/22/english-metric-units-and-open-xml/
The following is my solution:
using System.Text.RegularExpressions;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using SkiaSharp;
using A = DocumentFormat.OpenXml.Drawing;
using DW = DocumentFormat.OpenXml.Drawing.Wordprocessing;
using PIC = DocumentFormat.OpenXml.Drawing.Pictures;
/// <inheritdoc />
public class OpenXmlDocumentFieldProcessor : BaseDocumentFieldProcessor
{
/// <inheritdoc />
public OpenXmlDocumentFieldProcessor(DataHelper dataHelper) : base(dataHelper) { }
/// <inheritdoc />
public override UpdateTemplateViewModel ProcessDocumentFields(byte[] file, DocumentTemplate entity)
{
string tempFile = Path.GetTempFileName();
UpdateTemplateViewModel result = ApplyToTempFile(file, entity, tempFile);
if (result.Statuses == UpdateTemplateStatuses.Success)
{
result.StampedFile = File.ReadAllBytes(tempFile);
}
return result;
}
/// <summary>
/// Applies the field processing to a temp file.
/// </summary>
/// <param name="file">The file.</param>
/// <param name="entity">The entity.</param>
/// <param name="tempFile">The temp file.</param>
/// <returns>An UpdateTemplateViewModel.</returns>
private UpdateTemplateViewModel ApplyToTempFile(byte[] file, DocumentTemplate entity, string tempFile)
{
File.WriteAllBytes(tempFile, file);
using WordprocessingDocument doc = WordprocessingDocument.Open(tempFile, true);
MainDocumentPart mainPart = doc.MainDocumentPart ?? throw new ArgumentException("Invalid document");
var mergeFields = GetMergeFields(mainPart);
var customPropertyNames = mergeFields.Select(e => Regex.Replace(e.InnerText.Trim(), "DOCPROPERTY (.+) \\\\[*] MERGEFORMAT", "$1"))
.Distinct()
.Where(i => i.StartsWith('_'))
.ToList();
UpdateTemplateViewModel result = ValidateCustomProperties(customPropertyNames);
result.ClientName = null;
if (result.InvalidCustomProperties.Count > 0)
{
result.Statuses = UpdateTemplateStatuses.Failure;
return result;
}
var propertyMaps = GetDataForTemplate(customPropertyNames, entity);
propertyMaps.Add(new TemplatePropertyMap
{ IsImage = false, DocumentCustomPropertyName = "_{PreparedDate}", FieldValue = DateTime.Today.ToShortDateString() });
ReplaceMergeFields(mainPart, mergeFields, propertyMaps);
doc.Save();
result.Filename = entity.Filename;
result.Statuses = UpdateTemplateStatuses.Success;
return result;
}
/// <summary>
/// Replaces the merge fields.
/// </summary>
/// <param name="mainPart">The main part.</param>
/// <param name="mergeFields">The merge fields.</param>
/// <param name="fields">The fields.</param>
private static void ReplaceMergeFields(MainDocumentPart mainPart, IEnumerable<FieldCode> mergeFields,
IEnumerable<TemplatePropertyMap> fields)
{
var map = fields.ToDictionary(e => e.DocumentCustomPropertyName);
foreach (FieldCode field in mergeFields)
{
string fieldName = Regex.Replace(field.InnerText.Trim(), "DOCPROPERTY (.+) \\\\[*] MERGEFORMAT", "$1");
Run target = RemoveFieldCodeOverhead(field);
if (!map.ContainsKey(fieldName))
{
continue;
}
TemplatePropertyMap targetProperty = map[fieldName];
if (targetProperty.IsImage)
{
ReplaceMergeFieldWithImage(mainPart, target, targetProperty);
}
else
{
ReplaceMergeFieldWithText(target, targetProperty);
}
}
}
/// <summary>
/// Replaces the merge field with text.
/// </summary>
/// <param name="target">The target.</param>
/// <param name="targetProperty">The target property.</param>
private static void ReplaceMergeFieldWithText(OpenXmlElement target, TemplatePropertyMap targetProperty)
{
target.Append(new Text(targetProperty.FieldValue));
}
/// <summary>
/// Replaces the merge field with a base64-encoded image.
/// </summary>
/// <param name="mainPart">The main part.</param>
/// <param name="target">The target.</param>
/// <param name="targetProperty">The target property.</param>
private static void ReplaceMergeFieldWithImage(MainDocumentPart mainPart, OpenXmlElement target, TemplatePropertyMap targetProperty)
{
// Assumption: base64-encoded DataURl. We just need the base64. SkiaSharp will handle the rest.
string[] dataParts = targetProperty.FieldValue.Split(',');
string encodedData = dataParts.Last();
byte[] data = Convert.FromBase64String(encodedData);
SKImage originalImage = SKImage.FromEncodedData(data);
// Choosing 72 DPI and a 6in * 2in max size so supplied images don't disrupt templating. Works for POC. TODO: Turn into configuration value.
const int dpi = 72;
const long widthLimit = dpi * 6;
const long heightLimit = dpi * 2;
int resizedWidth = originalImage.Width;
int resizedHeight = originalImage.Height;
if (heightLimit < originalImage.Height || widthLimit < originalImage.Width)
{
float scaleHeight = heightLimit / (float)originalImage.Height;
float scaleWidth = widthLimit / (float)originalImage.Width;
float scale = Math.Min(scaleHeight, scaleWidth);
resizedWidth = (int)(originalImage.Width * scale);
resizedHeight = (int)(originalImage.Height * scale);
}
// Best reference for this EMU sizing: http://polymathprogrammer.com/2009/10/22/english-metric-units-and-open-xml/
long cx = resizedWidth * (long)((float)914400 / dpi);
long cy = resizedHeight * (long)((float)914400 / dpi);
ImagePart imagePart = mainPart.AddImagePart(ImagePartType.Png);
imagePart.FeedData(originalImage.Encode(SKEncodedImageFormat.Png, 100).AsStream());
target.Append(CreateDrawingElement(mainPart.GetIdOfPart(imagePart), targetProperty.DocumentCustomPropertyName, cx, cy));
}
/// <summary>
/// Removes the field code overhead.
/// </summary>
/// <param name="field">The field.</param>
/// <returns>A Run.</returns>
private static Run RemoveFieldCodeOverhead(OpenXmlElement field)
{
OpenXmlElement container = field.Parent?.Parent ?? throw new ArgumentException("Error resolving field replacement container");
container.RemoveAllChildren<ProofError>();
Run rFldParent = (Run)field.Parent;
var runs = new List<Run>
{
rFldParent.PreviousSibling<Run>(), // begin
rFldParent.NextSibling<Run>(),
};
// We're deleting until we hit the end delimiter for the Field.
do
{
runs.Add(runs.Last().NextSibling<Run>());
} while (runs.Last().ChildElements.OfType<FieldChar>().All(e => e.FieldCharType != FieldCharValues.End));
foreach (Run run in runs)
{
run.Remove();
}
rFldParent.RemoveAllChildren();
return rFldParent;
}
/// <summary>
/// Gets the merge fields.
/// </summary>
/// <param name="mainPart">The main part.</param>
/// <returns>A read only collection of FieldCodes.</returns>
private static IReadOnlyCollection<FieldCode> GetMergeFields(MainDocumentPart mainPart)
{
var mergeFields = new List<FieldCode>();
if (mainPart == null)
{
return mergeFields;
}
mergeFields.AddRange(mainPart.RootElement?.Descendants<FieldCode>() ?? new List<FieldCode>());
foreach (HeaderPart header in mainPart.HeaderParts)
{
mergeFields.AddRange(header.RootElement?.Descendants<FieldCode>() ?? new List<FieldCode>());
}
foreach (FooterPart footer in mainPart.FooterParts)
{
mergeFields.AddRange(footer.RootElement?.Descendants<FieldCode>() ?? new List<FieldCode>());
}
return mergeFields;
}
/// <summary>
/// Creates the Drawing element to house the supplied image by id.
/// </summary>
/// <param name="imagePartId">The id to the corresponding ImagePart within an OpenXML document</param>
/// <param name="name">The name of the image element</param>
/// <param name="cx">The width extent of the drawing in EMU's</param>
/// <param name="cy">The height extent of the drawing in EMU's</param>
/// <returns></returns>
private static Drawing CreateDrawingElement(string imagePartId, string name, long cx, long cy) => new(
new DW.Inline(new DW.Extent { Cx = cx, Cy = cy },
new DW.EffectExtent { LeftEdge = 0L, TopEdge = 0L, RightEdge = 0L, BottomEdge = 0L },
new DW.DocProperties { Id = 1U, Name = name },
new DW.NonVisualGraphicFrameDrawingProperties(new A.GraphicFrameLocks { NoChangeAspect = true }),
new A.Graphic(new A.GraphicData(new PIC.Picture(
new PIC.NonVisualPictureProperties(new PIC.NonVisualDrawingProperties { Id = 0U, Name = name },
new PIC.NonVisualPictureDrawingProperties()),
new PIC.BlipFill(
new A.Blip(new A.BlipExtensionList(new A.BlipExtension { Uri = "{28A0092B-C50C-407E-A947-70E740481C1C}" }))
{ Embed = imagePartId, CompressionState = A.BlipCompressionValues.HighQualityPrint },
new A.Stretch(new A.FillRectangle())),
new PIC.ShapeProperties(new A.Transform2D(new A.Offset { X = 0L, Y = 0L }, new A.Extents { Cx = cx, Cy = cy }),
new A.PresetGeometry(new A.AdjustValueList()) { Preset = A.ShapeTypeValues.Rectangle })))
{ Uri = "http://schemas.openxmlformats.org/drawingml/2006/picture" }))
{
DistanceFromTop = 0U, DistanceFromBottom = 0U, DistanceFromLeft = 0U, DistanceFromRight = 0U, EditId = "50D07946",
});
}
Related
Even PerformanceCounter is supported in .NET core, but it is not supported on the Ubuntu OS, so is there any way to get the system overall CPU and memory usage in a .NET core application (like the task manager shows in Windows)?
After some searching work, I did it by below codes (some codes are from the googling result). just FYI
internal static class CpuMemoryMetrics4LinuxUtils
{
private const int DigitsInResult = 2;
private static long totalMemoryInKb;
/// <summary>
/// Get the system overall CPU usage percentage.
/// </summary>
/// <returns>The percentange value with the '%' sign. e.g. if the usage is 30.1234 %,
/// then it will return 30.12.</returns>
public static double GetOverallCpuUsagePercentage()
{
// refer to https://stackoverflow.com/questions/59465212/net-core-cpu-usage-for-machine
var startTime = DateTime.UtcNow;
var startCpuUsage = Process.GetProcesses().Sum(a => a.TotalProcessorTime.TotalMilliseconds);
System.Threading.Thread.Sleep(500);
var endTime = DateTime.UtcNow;
var endCpuUsage = Process.GetProcesses().Sum(a => a.TotalProcessorTime.TotalMilliseconds);
var cpuUsedMs = endCpuUsage - startCpuUsage;
var totalMsPassed = (endTime - startTime).TotalMilliseconds;
var cpuUsageTotal = cpuUsedMs / (Environment.ProcessorCount * totalMsPassed);
return Math.Round(cpuUsageTotal * 100, DigitsInResult);
}
/// <summary>
/// Get the system overall memory usage percentage.
/// </summary>
/// <returns>The percentange value with the '%' sign. e.g. if the usage is 30.1234 %,
/// then it will return 30.12.</returns>
public static double GetOccupiedMemoryPercentage()
{
var totalMemory = GetTotalMemoryInKb();
var usedMemory = GetUsedMemoryForAllProcessesInKb();
var percentage = (usedMemory * 100) / totalMemory;
return Math.Round(percentage, DigitsInResult);
}
private static double GetUsedMemoryForAllProcessesInKb()
{
var totalAllocatedMemoryInBytes = Process.GetProcesses().Sum(a => a.PrivateMemorySize64);
return totalAllocatedMemoryInBytes / 1024.0;
}
private static long GetTotalMemoryInKb()
{
// only parse the file once
if (totalMemoryInKb > 0)
{
return totalMemoryInKb;
}
string path = "/proc/meminfo";
if (!File.Exists(path))
{
throw new FileNotFoundException($"File not found: {path}");
}
using (var reader = new StreamReader(path))
{
string line = string.Empty;
while (!string.IsNullOrWhiteSpace(line = reader.ReadLine()))
{
if (line.Contains("MemTotal", StringComparison.OrdinalIgnoreCase))
{
// e.g. MemTotal: 16370152 kB
var parts = line.Split(':');
var valuePart = parts[1].Trim();
parts = valuePart.Split(' ');
var numberString = parts[0].Trim();
var result = long.TryParse(numberString, out totalMemoryInKb);
return result ? totalMemoryInKb : throw new FileFormatException($"Cannot parse 'MemTotal' value from the file {path}.");
}
}
throw new FileFormatException($"Cannot find the 'MemTotal' property from the file {path}.");
}
}
}
You have to rely on the OS specific utilities that provide CPU and memory information.
Run the command from your application and read/parse output returned.
I found an article which looks in line with what you are trying to achieve.
Reading Windows and Linux memory metrics with .NET Core
I have a Bar chart on Kentico reporting section. And I know Kentico uses Microsoft Chart Controls. Microsoft Chart controls have the capability of creating a trending line on Bar graph - But I do see any option how I can utilize those on Kentico Reporting.
Is there any option there on reporting tool to get this trending line ?
If there is no option can anybody suggest anything else ?
Using custom module is the last option for me to try. If anybody has any specific suggestion regarding this custom module, please share that, too.
I am using Kentico 7.
Got it working the way Brend suggested however the mean is not coming up
ChartArea area = graph.ChartControl.ChartAreas[chartAreas - 1];
StripLine line = new StripLine();
// Set threshold line so that it is only shown once
line.Interval = 0;
// Set the threshold line to be drawn at the calculated mean of the first series
line.IntervalOffset = graph.ChartControl.DataManipulator.Statistics.Mean(graph.ChartControl.Series[0].Name);
line.BackColor = System.Drawing.Color.DarkGreen;
line.StripWidth = 0.25;
// Set text properties for the threshold line
//line.Text = "Mean";
line.ForeColor = System.Drawing.Color.Black;
// Add strip line to the chart
area.AxisY.StripLines.Add(line);
Also, for other trend line I am using bellow code, again having no luck as it looks like the datapoints are not set at the point where my code runs :
int chartAreas = graph.ChartControl.ChartAreas.Count;
if (chartAreas > 0)
{
graph.ChartControl.Series.Add("TrendLine");
graph.ChartControl.Series["TrendLine"].ChartType = SeriesChartType.Line;
graph.ChartControl.Series["TrendLine"].BorderWidth = 3;
graph.ChartControl.Series["TrendLine"].Color = System.Drawing.Color.Red;
// Line of best fit is linear
string typeRegression = "Linear";//"Exponential";//
// The number of days for Forecasting
string forecasting = "1";
// Show Error as a range chart.
string error = "false";
// Show Forecasting Error as a range chart.
string forecastingError = "false";
// Formula parameters
string parameters = typeRegression + ',' + forecasting + ',' + error + ',' + forecastingError;
graph.ChartControl.Series[0].Sort(PointSortOrder.Ascending, "X");
// Create Forecasting Series.
graph.ChartControl.DataManipulator.FinancialFormula(FinancialFormula.Forecasting, parameters, graph.ChartControl.Series[0], graph.ChartControl.Series["TrendLine"]);
}
The actual issue, I guess, is not having the graph.ChartControl.Series[0] at the place I am running my TrendLine generation code. Any idea how can I get it ?
Report charts are rendered through \CMSModules\Reporting\Controls\ReportGraph.ascx
You can modify the method GetReportGraph and add additional setup to the chart control ucChart based on some condition, e.g. the report name and chart name (you will have to hardcode that)
Note that you will need to modify Kentico code directly, so keep the changes at the lowest possible level, I recommend:
Put the extra setup code to an external class
Call it with just one extra line of code
Add comment to mark that extra line of code as customization
e.g.:
/* YourCompany */
MyChartExtender.ExtendChart(ucChart, ...);
/* YourCompany end */
Make sure you note that change for future upgrades
I've modified the controls before and you can use this code in the GetReportGraph() method just before enabling the subscription.
// apply the trendline
if (TrendValue > 0)
{
int chartAreas = graph.ChartControl.ChartAreas.Count;
if (chartAreas > 0)
{
ChartArea area = graph.ChartControl.ChartAreas[chartAreas - 1];
StripLine line = new StripLine();
line.IntervalOffset = TrendValue;
line.BorderColor = System.Drawing.ColorTranslator.FromHtml(TrendColor);
line.BackColor = System.Drawing.ColorTranslator.FromHtml(TrendColor);
line.StripWidth = TrendLineWidth;
line.ToolTip = TrendToolTip;
line.Text = TrendText;
line.TextLineAlignment = trendLineAlignment;
line.TextOrientation = TextOrientation.Horizontal;
line.TextAlignment = trendTextAlignment;
area.AxisY.StripLines.Add(line);
}
}
Of course you'll have to add the appropriate properties and pass the values through from the rest of the pages/controls using this control.
#region Trending
/// <summary>
/// Value for the single trendline for whole chart
/// </summary>
public int TrendValue
{
get
{
return mTrendValue;
}
set
{
mTrendValue = value;
}
}
/// <summary>
/// Color of the trend line in hex format (i.e. #0000FF)
/// </summary>
public string TrendColor
{
get
{
return mTrendColor;
}
set
{
mTrendColor = value;
}
}
/// <summary>
/// Tool tip of the trend line
/// </summary>
public string TrendToolTip
{
get
{
return mTrendToolTip;
}
set
{
mTrendToolTip = value;
}
}
/// <summary>
/// Text of the trend line
/// </summary>
public string TrendText
{
get
{
return mTrendText;
}
set
{
mTrendText = value;
}
}
/// <summary>
/// Trend line width
/// </summary>
public double TrendLineWidth
{
get
{
return mTrendLineWidth;
}
set
{
mTrendLineWidth = value;
}
}
string mTrendLineAlignment;
public string TrendLineAlignment
{
get
{
return mTrendLineAlignment;
}
set
{
mTrendLineAlignment = value;
}
}
private System.Drawing.StringAlignment trendLineAlignment
{
get
{
switch (TrendLineAlignment)
{
case "center":
return System.Drawing.StringAlignment.Center;
case "near":
return System.Drawing.StringAlignment.Near;
case "far":
return System.Drawing.StringAlignment.Far;
default:
return System.Drawing.StringAlignment.Near;
}
}
}
string mTrendTextAlignment;
public string TrendTextAlignment
{
get
{
return mTrendTextAlignment;
}
set
{
mTrendTextAlignment = value;
}
}
private System.Drawing.StringAlignment trendTextAlignment
{
get
{
switch (TrendTextAlignment)
{
case "center":
return System.Drawing.StringAlignment.Center;
case "near":
return System.Drawing.StringAlignment.Near;
case "far":
return System.Drawing.StringAlignment.Far;
default:
return System.Drawing.StringAlignment.Near;
}
}
}
#endregion
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
XNA - File not found problem
Here I am trying to load a Round.png file in windows phone 7 application project. I don't know how to load this image during run time. I am really sorry if this is a silly question as i m a newbie in windows app development. Please help...
Thanks in advance!!!
/// <summary>
/// This is the main type for your game
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Texture2D texRound;
Rectangle HitRegion;
bool isSelected = false;
TouchCollection touches = TouchPanel.GetState();
//start position of round, in the center of screen
int positionX = 400;
int positionY = 240;
//random number Axis X and Y
Random randomX;
Random randomY;
//the range for random number of start and end of X, Y
int startX, endX;
int startY, endY;
//total time
float milliseconds = 0f;
//score count
int count = 0;
//game font
SpriteFont font;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
// Frame rate is 30 fps by default for Windows Phone.
TargetElapsedTime = TimeSpan.FromTicks(333333);
// Extend battery life under lock.
InactiveSleepTime = TimeSpan.FromSeconds(1);
}
/// <summary>
/// Allows the game to perform any initialization it needs to before starting to run.
/// This is where it can query for any required services and load any non-graphic
/// related content. Calling base.Initialize will enumerate through any components
/// and initialize them as well.
/// </summary>
protected override void Initialize()
{
// TODO: Add your initialization logic here
base.Initialize();
}
/// <summary>
/// LoadContent will be called once per game and is the place to load
/// all of your content.
/// </summary>
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
texRound = Content.Load<Texture2D>("Round");
randomX = new Random();
randomY = new Random();
// The X axis bound range of touch for ball
startX = texRound.Width;
endX = GraphicsDevice.Viewport.Width - texRound.Width;
// The X axis bound range of touch for ball
startY = texRound.Height;
endY = GraphicsDevice.Viewport.Height - texRound.Height;
// Define the HitRegion of ball in the middle of touchscreen
HitRegion = new Rectangle(positionX - texRound.Width / 2,
positionY - texRound.Height / 2, texRound.Width,
texRound.Height);
// Load the font definition file
font = Content.Load<SpriteFont>("gamefont");
// TODO: use this.Content to load your game content here
}
/// <summary>
/// UnloadContent will be called once per game and is the place to unload
/// all content.
/// </summary>
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
/// <summary>
/// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
// TODO: Add your update logic here
// Accumulate the elapsed milliseconds every frame
milliseconds +=
(float)gameTime.ElapsedGameTime.TotalMilliseconds;
if (milliseconds > 1000)
{
// When the milliseconds greater than 1000 milliseconds,
// randomly locate a new position for the ball
HitRegion.X = randomX.Next(startX, endX + 1);
HitRegion.Y = randomY.Next(startY, endY + 1);
// Reset the milliseconds to zero for new milliseconds
// count
// make the ball not been selected
milliseconds = 0f;
if (isSelected)
isSelected = false;
}
base.Update(gameTime);
Point touchPoint = new Point((int)touches[0].Position.X, (int)touches[0].Position.Y);
if (HitRegion.Contains(touchPoint))
{
isSelected = true;
count++;
}
else
{
isSelected = false;
}
}
/// <summary>
/// This is called when the game should draw itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Green);
// TODO: Add your drawing code here
spriteBatch.Begin();
if (isSelected)
{
spriteBatch.Draw(texRound, HitRegion, Color.Red);
}
else
{
spriteBatch.Draw(texRound, HitRegion, Color.White);
}
spriteBatch.DrawString(font, "Score:" + count.ToString(),
new Vector2(0f, 0f), Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
}
The path in the inner exception looks correct: \"Content\\Round.xnb\", the extra slashes are just because it's escaped. This is a relative path from your executable, be default it's something like: Visual Studio Projects\MyCoolGame\MyCoolGame\bin\x86\Debug\Content\Round.xnb. You should be comparing this path to the the real path to the Round.xnb file, not the png.
If Round.xnb does not exist anywhere, then your png file isn't part of Content project, or the Content project isn't being built
If the file does exist, but is in a subdirectory, specify the name of the subdirectory when you load the texture like: Content.Load<Texture2D>("myDir\\Round");
Since the inner exception isn't looking for "Round.png.xnb", you have your names up correctly and #Ricket's answer won't help you any further.
I'm assuming you correctly put the Round.png file in your Content directory (or project) and it's being compiled properly, as per default settings.
Right-click the file and open its properties. Check the "Name" field. That's what Content.Load<> is expecting. So for example, if you drag a file called "Circular.png" into the Content folder, then it will be auto-named "Circular", but if you rename the file to "Round.png", typically it will remain named "Circular", leaving you to manually change the name in the properties and update all references in your code.
i'm having some issues getting the .Filter() method to work in subsonic, and i'm constantly getting errors like the one below:
Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.
Line 36: bool remove = false;
Line 37: System.Reflection.PropertyInfo pi = o.GetType().GetProperty(w.ColumnName);
Line 38: if (pi.CanRead)
Line 39: {
Line 40: object val = pi.GetValue(o, null);
i'm making calls like the one below- is this the corrent way to use it? There seems to be no documentation on the use of this method
NavCollection objTopLevelCol = objNavigation.Where(Nav.Columns.NavHigherID,Comparison.Equals, 0).Filter();
thanks in advance
The value you filter against needs to be the Property Name, not the database column name.
You might try this:
lCol = objNavigation.Where(Nav.HigherIDColumn.PropertyName,Comparison.Equals, 0).Filter();
Or here's a slightly more verbose method that works for me based on custom overrides of the .Filter() method. It seemed to work better (for me at least) by explicitly creating the Where beforehand:
SubSonic.Where w = new SubSonic.Where();
w.ColumnName = Nav.HigherIDColumn.PropertyName;
w.Comparison = SubSonic.Comparison.NotIn;
w.ParameterValue = new string[] { "validvalue1", "validvalue2" };
lCol = objNavigation.Filter(w, false);
Here's the overrides:
/// <summary>
/// Filters an existing collection based on the set criteria. This is an in-memory filter.
/// All existing wheres are retained.
/// </summary>
/// <returns>NavCollection</returns>
public NavCollection Filter(SubSonic.Where w)
{
return Filter(w, false);
}
/// <summary>
/// Filters an existing collection based on the set criteria. This is an in-memory filter.
/// Existing wheres can be cleared if not needed.
/// </summary>
/// <returns>NavCollection</returns>
public NavCollection Filter(SubSonic.Where w, bool clearWheres)
{
if (clearWheres)
{
this.wheres.Clear();
}
this.wheres.Add(w);
return Filter();
}
/// <summary>
/// Filters an existing collection based on the set criteria. This is an in-memory filter.
/// Thanks to developingchris for this!
/// </summary>
/// <returns>NavCollection</returns>
public NavCollection Filter()
{
for (int i = this.Count - 1; i > -1; i--)
{
Nav o = this[i];
foreach (SubSonic.Where w in this.wheres)
{
bool remove = false;
System.Reflection.PropertyInfo pi = o.GetType().GetProperty(w.ColumnName);
if (pi != null && pi.CanRead)
{
object val = pi.GetValue(o, null);
if (w.ParameterValue is Array)
{
Array paramValues = (Array)w.ParameterValue;
foreach (object arrayVal in paramValues)
{
remove = !Utility.IsMatch(w.Comparison, val, arrayVal);
if (remove)
break;
}
}
else
{
remove = !Utility.IsMatch(w.Comparison, val, w.ParameterValue);
}
}
if (remove)
{
this.Remove(o);
break;
}
}
}
return this;
}
And SubSonic 2.0 doesn't actually support In/NotIn for the IsMatch function, so here's the customized version that does (in SubSonic\Utility.cs):
public static bool IsMatch(SubSonic.Comparison compare, object objA, object objB)
{
if (objA.GetType() != objB.GetType())
return false;
bool isIntegerVal = (typeof(int) == objA.GetType());
bool isDateTimeVal = (typeof(DateTime) == objA.GetType());
switch (compare)
{
case SubSonic.Comparison.In:
case SubSonic.Comparison.Equals:
if (objA.GetType() == typeof(string))
return IsMatch((string)objA, (string)objB);
else
return objA.Equals(objB);
case SubSonic.Comparison.NotIn:
case SubSonic.Comparison.NotEquals:
return !objA.Equals(objB);
case SubSonic.Comparison.Like:
return objA.ToString().Contains(objB.ToString());
case SubSonic.Comparison.NotLike:
return !objA.ToString().Contains(objB.ToString());
case SubSonic.Comparison.GreaterThan:
if (isIntegerVal)
{
return ((int)objA > (int)objB);
}
else if (isDateTimeVal)
{
return ((DateTime)objA > (DateTime)objB);
}
break;
case SubSonic.Comparison.GreaterOrEquals:
if (isIntegerVal)
{
return ((int)objA >= (int)objB);
}
else if (isDateTimeVal)
{
return ((DateTime)objA >= (DateTime)objB);
}
break;
case SubSonic.Comparison.LessThan:
if (isIntegerVal)
{
return ((int)objA < (int)objB);
}
else if (isDateTimeVal)
{
return ((DateTime)objA < (DateTime)objB);
}
break;
case SubSonic.Comparison.LessOrEquals:
if (isIntegerVal)
{
return ((int)objA <= (int)objB);
}
else if (isDateTimeVal)
{
return ((DateTime)objA <= (DateTime)objB);
}
break;
}
return false;
}
IF you're using .net 3.5 you could just do this with a lambda function:
NavCollection objTopLevelCol =
objNavigation.Where(nav => nav.NavHigherID == 0);
Filter is designed to work on a collection - is "objNavigation" a collection? The problem you're running into is that the criteria for Filter() can't be met with the column name "NavHigherID".
I was having same prob, try doing your filter like this :
lCol = objNavigation.Where("NavHigherID",Comparison.Equals, 0).Filter();
I'm trying to configure the Quick Launch menu to only display the ancestors and descendant nodes of the currently select node. The menu also needs to display all the childern of the root node. More simply:
Given a site map of:
RootSite
---SubSite1 = navigation set at "Display the current site, the navigation items below the current site, and the current site's siblings"
-----Heading1 = navigation set at "Display the same navigation items as the parent site"
-------Page1 = navigation set at "Display the same navigation items as the parent site"
-------Page2 = navigation set at "Display the same navigation items as the parent site"
-----Heading2 = navigation set at "Display the same navigation items as the parent site"
---SubSite2 = navigation set at "Display the current site, the navigation items below the current site, and the current site's siblings"
-----Heading1 = navigation set at "Display the same navigation items as the parent site"
SiteMapProvider configuration:
<PublishingNavigation:PortalSiteMapDataSource ID="SiteMapDS" Runat="server"
SiteMapProvider="CurrentNavSiteMapProvider" EnableViewState="true"
StartFromCurrentNode="true" ShowStartingNode="false"/>
The expected and actual behavior of the Quick Launch menu displayed at SubSite1 is:
---SubSite1
-----Heading1
-------Page1
-------Page2
-----Heading2
---SubSite2
The expected behavior of the menu after navigating to Heading1 of SubSite2:
---SubSite1
---SubSite2
-----Heading1
What I actually see after navigating to Heading1 of SubSite2:
---SubSite1
-----Heading1
-------Page1
-------Page2
-----Heading2
---SubSite2
-----Heading1
This does not match what I expect to see if I set the Heading1 navigation to "Display the
same navigation items as the parent site" and SubSite2 is set to "Display the current site, the navigation items below the current site, and the current site's siblings". I expect
Heading1 to inherit the navigation item of SubSite2 with the SubSite1 items collapsed from view. I've also played with the various
Trim... attributes without success. Any help will be greatly appreciated!
I followed #Nat's guidance into the murky world Sharepoint webparts to achieve the behavior I described above. My approach was to roll my own version of the MossMenu webpart that Microsoft has released through the ECM Team Blog. This code is based on the native AspMenu control. I used this control to "intercept" the native SiteMapDataSource injected into through DataSourceId attribute in the markup and create a new XML data source to exhibit the desired behavior. I've included the final source code at the end of this wordy answer. Here are the bits from the master page markup:
<%# Register TagPrefix="myCustom" Namespace="YourCompany.CustomWebParts"
Assembly="YourCompany.CustomWebParts, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=9f4da00116c38ec5" %>
...
<myCustom:MossMenu ID="CurrentNav" runat="server" datasourceID="SiteMapDS"
orientation="Vertical" UseCompactMenus="true" StaticDisplayLevels="6"
MaximumDynamicDisplayLevels="0" StaticSubMenuIndent="5" ItemWrap="false"
AccessKey="3" CssClass="leftNav"
SkipLinkText="<%$Resources:cms,masterpages_skiplinktext%>">
<LevelMenuItemStyles>
<asp:MenuItemStyle CssClass="Nav" />
<asp:MenuItemStyle CssClass="SecNav" />
</LevelMenuItemStyles>
<StaticHoverStyle CssClass="leftNavHover"/>
<StaticSelectedStyle CssClass="leftNavSelected"/>
<DynamicMenuStyle CssClass="leftNavFlyOuts" />
<DynamicMenuItemStyle CssClass="leftNavFlyOutsItem"/>
<DynamicHoverStyle CssClass="leftNavFlyOutsHover"/>
</myCustom:MossMenu>
<PublishingNavigation:PortalSiteMapDataSource ID="SiteMapDS" Runat="server"
SiteMapProvider="CurrentNavSiteMapProvider" EnableViewState="true"
StartFromCurrentNode="true" ShowStartingNode="false"/>
...
I followed the excellent step-by-step instructions to create my custom web part in the comments section of the MossMenu webpart at "Wednesday, September 19, 2007 7:20 AM by Roel". In my googling, I also found something to configure a Sharepoint site to display exceptions in the same lovely way that ASP.NET does by making the web.config changes here.
I decided to call my custom behavior a "compact menu" so I created a UseCompactMenus property on the control. If you don't set this attribute in the markup to true, the control will behave identically to an AspMenu control.
My application has the user always starting from the home page at the site map root. I can have the custom control store the initial (complete) site map when the root page is displayed. This is stored in a static string for use in the customizing behavior. If you application doesn't follow this assumption, the control will not work as expected.
On the initial application page, only the direct child pages to the root page are displayed in the menu. Clicking on these menu nodes will open all the child nodes under it but keeps the sibling nodes "closed". If you click on one of the other sibling nodes, it collapses the current node and it opens the newly selected node. That's it, enjoy!!
using System;
using System.Text;
using System.ComponentModel;
using System.Collections.Generic;
using System.Security.Permissions;
using System.Xml;
using System.Xml.Serialization;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.Design.WebControls;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.Security;
namespace YourCompany.CustomWebParts
{
[AspNetHostingPermission(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
[SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
[SharePointPermission(SecurityAction.InheritanceDemand, ObjectModel = true)]
[Designer(typeof(MossMenuDesigner))]
[ToolboxData("<{0}:MossMenu runat=\"server\" />")]
public class MossMenu : System.Web.UI.WebControls.Menu
{
private string idPrefix;
// a url->menuItem dictionary
private Dictionary<string, System.Web.UI.WebControls.MenuItem> menuItemDictionary =
new Dictionary<string, System.Web.UI.WebControls.MenuItem>(StringComparer.OrdinalIgnoreCase);
private bool customSelectionEnabled = true;
private bool selectStaticItemsOnly = true;
private bool performTargetBinding = true;
//** Variables used for compact menu behavior **//
private bool useCompactMenus = false;
private static bool showStartingNode;
private static string originalSiteMap;
/// <summary>
/// Controls whether or not the control performs compacting of the site map to display only ancestor and child nodes of the selected and first level root childern.
/// </summary>
[Category("Behavior")]
public bool UseCompactMenus
{
get
{
return this.useCompactMenus;
}
set
{
this.useCompactMenus = value;
}
}
/// <summary>
/// Controls whether or not the control performs custom selection/highlighting.
/// </summary>
[Category("Behavior")]
public bool CustomSelectionEnabled
{
get
{
return this.customSelectionEnabled;
}
set
{
this.customSelectionEnabled = value;
}
}
/// <summary>
/// Controls whether only static items may be selected or if
/// dynamic (fly-out) items may be selected too.
/// </summary>
[Category("Behavior")]
public bool SelectStaticItemsOnly
{
get
{
return this.selectStaticItemsOnly;
}
set
{
this.selectStaticItemsOnly = value;
}
}
/// <summary>
/// Controls whether or not to bind the Target property of any menu
/// items to the Target property in the SiteMapNode's Attributes
/// collection.
/// </summary>
[Category("Behavior")]
public bool PerformTargetBinding
{
get
{
return this.performTargetBinding;
}
set
{
this.performTargetBinding = value;
}
}
/// <summary>
/// Gets the ClientID of this control.
/// </summary>
public override string ClientID
{
[SharePointPermission(SecurityAction.Demand, ObjectModel = true)]
get
{
if (this.idPrefix == null)
{
this.idPrefix = SPUtility.GetNewIdPrefix(this.Context);
}
return SPUtility.GetShortId(this.idPrefix, this);
}
}
[SharePointPermission(SecurityAction.Demand, ObjectModel = true)]
protected override void OnMenuItemDataBound(MenuEventArgs e)
{
base.OnMenuItemDataBound(e);
if (this.customSelectionEnabled)
{
// store in the url->item dictionary
this.menuItemDictionary[e.Item.NavigateUrl] = e.Item;
}
if (this.performTargetBinding)
{
// try to bind to the Target property if the data item is a SiteMapNode
SiteMapNode smn = e.Item.DataItem as SiteMapNode;
if (smn != null)
{
string target = smn["Target"];
if (!string.IsNullOrEmpty(target))
{
e.Item.Target = target;
}
}
}
}
/// <id guid="08e034e7-5872-4a31-a771-84cac1dcd53d" />
/// <owner alias="MarkWal">
/// </owner>
[SharePointPermission(SecurityAction.Demand, ObjectModel = true)]
protected override void OnPreRender(System.EventArgs e)
{
SiteMapDataSource dataSource = this.GetDataSource() as SiteMapDataSource;
SiteMapProvider provider = (dataSource != null) ? dataSource.Provider : null;
if (useCompactMenus && dataSource != null && provider != null)
{
showStartingNode = dataSource.ShowStartingNode;
SiteMapNodeCollection rootChildNodes = provider.RootNode.ChildNodes;
if (provider.CurrentNode.Equals(provider.RootNode))
{
//** Store original site map for future use in compacting menus **//
if (originalSiteMap == null)
{
//Store original SiteMapXML for future adjustments:
XmlDocument newSiteMapDoc = new XmlDocument();
newSiteMapDoc.LoadXml("<?xml version='1.0' ?>"
+ "<siteMapNode title='" + provider.RootNode.Title
+ "' url='" + provider.RootNode.Url
+ "' />");
foreach (SiteMapNode node in rootChildNodes)
{
XmlNode newNode = GetXmlSiteMapNode(newSiteMapDoc.DocumentElement, node);
newSiteMapDoc.DocumentElement.AppendChild(newNode);
//Create XML for all the child nodes for selected menu item:
NavigateSiteMap(newNode, node);
}
originalSiteMap = newSiteMapDoc.OuterXml;
}
//This is set to only display the child nodes of the root node on first view:
this.StaticDisplayLevels = 1;
}
else
{
//
//Adjust site map for this page
//
XmlDocument newSiteMapDoc = InitializeNewSiteMapXml(provider, rootChildNodes);
//Clear the current default site map:
this.DataSourceID = null;
//Create the new site map data source
XmlDataSource newSiteMap = new XmlDataSource();
newSiteMap.ID = "XmlDataSource1";
newSiteMap.EnableCaching = false; //Required to prevent redisplay of the previous menu
//Add bindings for dynamic site map:
MenuItemBindingCollection bindings = this.DataBindings;
bindings.Clear();
MenuItemBinding binding = new MenuItemBinding();
binding.DataMember = "siteMapNode";
binding.TextField = "title";
binding.Text = "title";
binding.NavigateUrlField = "url";
binding.NavigateUrl = "url";
binding.ValueField = "url";
binding.Value = "url";
bindings.Add(binding);
//Bind menu to new site map:
this.DataSource = newSiteMap;
//Assign the newly created dynamic site map:
((XmlDataSource)this.DataSource).Data = newSiteMapDoc.OuterXml;
/** this expression removes the root if initialized: **/
if (!showStartingNode)
((XmlDataSource)this.DataSource).XPath = "/siteMapNode/siteMapNode";
/** Re-initialize menu data source with new site map: **/
this.DataBind();
/** Find depth of current node: **/
int depth = 0;
SiteMapNode currNode = provider.CurrentNode;
do
{
depth++;
currNode = currNode.ParentNode;
}
while (currNode != null);
//Set the StaticDisplayLevels to match the current depth:
if (depth >= this.StaticDisplayLevels)
this.StaticDisplayLevels = depth;
}
}
base.OnPreRender(e);
// output some script to override the default menu flyout behaviour; this helps to avoid
// intermittent "Operation Aborted" errors
Page.ClientScript.RegisterStartupScript(
typeof(MossMenu),
"overrideMenu_HoverStatic",
"if (typeof(overrideMenu_HoverStatic) == 'function' && typeof(Menu_HoverStatic) == 'function')\n" +
"{\n" +
"_spBodyOnLoadFunctionNames.push('enableFlyoutsAfterDelay');\n" +
"Menu_HoverStatic = overrideMenu_HoverStatic;\n" +
"}\n",
true);
// output some script to avoid a known issue with SSL Termination and the ASP.NET
// Menu implementation. http://support.microsoft.com/?id=910444
Page.ClientScript.RegisterStartupScript(
typeof(MossMenu),
"MenuHttpsWorkaround_" + this.ClientID,
this.ClientID + "_Data.iframeUrl='/_layouts/images/blank.gif';",
true);
// adjust the fly-out indicator arrow direction for locale if not already set
if (this.Orientation == System.Web.UI.WebControls.Orientation.Vertical &&
((string.IsNullOrEmpty(this.StaticPopOutImageUrl) && this.StaticEnableDefaultPopOutImage) ||
(string.IsNullOrEmpty(this.DynamicPopOutImageUrl) && this.DynamicEnableDefaultPopOutImage)))
{
SPWeb currentWeb = SPContext.Current.Web;
if (currentWeb != null)
{
uint localeId = currentWeb.Language;
bool isBidiWeb = SPUtility.IsRightToLeft(currentWeb, currentWeb.Language);
string arrowUrl = "/_layouts/images/" + (isBidiWeb ? "largearrowleft.gif" : "largearrowright.gif");
if (string.IsNullOrEmpty(this.StaticPopOutImageUrl) && this.StaticEnableDefaultPopOutImage)
{
this.StaticPopOutImageUrl = arrowUrl;
}
if (string.IsNullOrEmpty(this.DynamicPopOutImageUrl) && this.DynamicEnableDefaultPopOutImage)
{
this.DynamicPopOutImageUrl = arrowUrl;
}
}
}
if (provider == null)
{
// if we're not attached to a SiteMapDataSource we'll just leave everything alone
return;
}
else if (this.customSelectionEnabled)
{
MenuItem selectedMenuItem = this.SelectedItem;
SiteMapNode currentNode = provider.CurrentNode;
// if no menu item is presently selected, we need to work our way up from the current
// node until we can find a node in the menu item dictionary
while (selectedMenuItem == null && currentNode != null)
{
this.menuItemDictionary.TryGetValue(currentNode.Url, out selectedMenuItem);
currentNode = currentNode.ParentNode;
}
if (this.selectStaticItemsOnly)
{
// only static items may be selected, keep moving up until we find an item
// that falls within the static range
while (selectedMenuItem != null && selectedMenuItem.Depth >= this.StaticDisplayLevels)
{
selectedMenuItem = selectedMenuItem.Parent;
}
// if we found an item to select, go ahead and select (highlight) it
if (selectedMenuItem != null && selectedMenuItem.Selectable)
{
selectedMenuItem.Selected = true;
}
}
}
}
private XmlDocument InitializeNewSiteMapXml(SiteMapProvider provider, SiteMapNodeCollection rootChildNodes)
{
/** Find the level 1 ancestor node of the current node: **/
SiteMapNode levelOneAncestorOfSelectedNode = null;
SiteMapNode currNode = provider.CurrentNode;
do
{
levelOneAncestorOfSelectedNode = (currNode.ParentNode == null ? levelOneAncestorOfSelectedNode : currNode);
currNode = currNode.ParentNode;
}
while (currNode != null);
/** Initialize base SiteMapXML **/
XmlDocument newSiteMapDoc = new XmlDocument();
newSiteMapDoc.LoadXml(originalSiteMap);
/** Prune out the childern nodes that shouldn't display: **/
currNode = provider.CurrentNode;
do
{
if (currNode.ParentNode != null)
{
SiteMapNodeCollection currNodeSiblings = currNode.ParentNode.ChildNodes;
foreach (SiteMapNode siblingNode in currNodeSiblings)
{
if (siblingNode.HasChildNodes)
{
if (provider.CurrentNode.Equals(siblingNode))
{
//Remove all the childerns child nodes from display:
SiteMapNodeCollection currNodesChildren = siblingNode.ChildNodes;
foreach (SiteMapNode childNode in currNodesChildren)
{
XmlNode currentXmNode = GetCurrentXmlNode(newSiteMapDoc, childNode);
DeleteChildNodes(currentXmNode);
}
}
else if (!provider.CurrentNode.IsDescendantOf(siblingNode)
&& !levelOneAncestorOfSelectedNode.Equals(siblingNode))
{
XmlNode currentXmNode = GetCurrentXmlNode(newSiteMapDoc, siblingNode);
DeleteChildNodes(currentXmNode);
}
}
}
}
currNode = currNode.ParentNode;
}
while (currNode != null);
return newSiteMapDoc;
}
private XmlNode GetCurrentXmlNode(XmlDocument newSiteMapDoc, SiteMapNode node)
{
//Find this node in the original site map:
XmlNode currentXmNode = newSiteMapDoc.DocumentElement.SelectSingleNode(
"//siteMapNode[#url='"
+ node.Url
+ "']");
return currentXmNode;
}
private void DeleteChildNodes(XmlNode currentXmNode)
{
if (currentXmNode != null && currentXmNode.HasChildNodes)
{
//Remove child nodes:
XmlNodeList xmlNodes = currentXmNode.ChildNodes;
int lastNodeIndex = xmlNodes.Count - 1;
for (int i = lastNodeIndex; i >= 0; i--)
{
currentXmNode.RemoveChild(xmlNodes[i]);
}
}
}
private XmlNode GetXmlSiteMapNode(XmlNode currentDocumentNode, SiteMapNode currentNode)
{
XmlElement newNode = currentDocumentNode.OwnerDocument.CreateElement("siteMapNode");
XmlAttribute newAttr = currentDocumentNode.OwnerDocument.CreateAttribute("title");
newAttr.InnerText = currentNode.Title;
newNode.Attributes.Append(newAttr);
newAttr = currentDocumentNode.OwnerDocument.CreateAttribute("url");
newAttr.InnerText = currentNode.Url;
newNode.Attributes.Append(newAttr);
return newNode;
}
private void NavigateSiteMap(XmlNode currentDocumentNode, SiteMapNode currentNode)
{
foreach (SiteMapNode node in currentNode.ChildNodes)
{
//Add this node to structure:
XmlNode newNode = GetXmlSiteMapNode(currentDocumentNode, node);
currentDocumentNode.AppendChild(newNode);
if (node.HasChildNodes)
{
//Make a recursive call to add any child nodes:
NavigateSiteMap(newNode, node);
}
}
}
}
[PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2117:AptcaTypesShouldOnlyExtendAptcaBaseTypes")]
public sealed class MossMenuDesigner : MenuDesigner
{
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
protected override void DataBind(BaseDataBoundControl dataBoundControl)
{
try
{
dataBoundControl.DataBind();
}
catch
{
base.DataBind(dataBoundControl);
}
}
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public override string GetDesignTimeHtml()
{
System.Web.UI.WebControls.Menu menu = (System.Web.UI.WebControls.Menu)ViewControl;
int oldDisplayLevels = menu.MaximumDynamicDisplayLevels;
string designTimeHtml = string.Empty;
try
{
menu.MaximumDynamicDisplayLevels = 0;
// ASP.NET MenuDesigner has some dynamic/static item trick in design time
// to show dynamic item in design time. We only want to show preview without
// dynamic menu items.
designTimeHtml = base.GetDesignTimeHtml();
}
catch (Exception e)
{
designTimeHtml = GetErrorDesignTimeHtml(e);
}
finally
{
menu.MaximumDynamicDisplayLevels = oldDisplayLevels;
}
return designTimeHtml;
}
}
}
I personally don't like the html that the default menu provides (table based layout).
Fortunately the SharePoint team has released the code for that control.
What we have done is to include that code in a project and have overridden the render method to do whatever we want. This give you the flexibility to define the exact relationship between parents that needs to be display as well as setting the styles on any divs you create.
On the down side you are now coding, not configuring and a change needs to be made to the master page you are using to use the control.
Worth it in my opinion. This is now a standard change we make for any site.
The approach we used to accomplish the affect you are looking for was to use the CSS Friendly Control Adapters. The adapters change the HTML that is rendered without changing the controls you used on your pages. You may need to tweak the menu adapter a little bit in order to get the layout you want. It only took a few lines of code for us. Once you get that working, you can use CSS to obtain the behavior you describe.