Convert a Silverlight text to a path - silverlight

Is there any way to render a Silverlight text to a path or graphics object at runtime? I know this can be done using the design tools, but I want to be able to do this on the fly.
I've seen an example that calls a webservice which uses WPF constructs to convert a WPF FormattedText object to a PathGeometry, but those objects aren't available in Silverlight.
I'm pretty sure this just isn't supported in Silverlight, but thought it was worth asking.

As you suspected, you'd have to do it server-side to convert the text to a PathGeometry which is supported in Silverlight.
What are you trying to achieve?

Here is the code for creating a SilverLight path Geometry dynamically.
or creating a SilverLight path from a string.
Just paste below method in a new class or in existing class and it will be ready to use.
you can find a sample code to use this functions at bottom of the method.
/// Method tested with SilverLight 2.0
// this method will generates path with the data string
public PathGeometry getPathGeometry(string data)
{
PathGeometry pg = new PathGeometry();
PathSegmentCollection psc = new PathSegmentCollection();
PathFigure pf = new PathFigure();
PathFigureCollection pfc = new PathFigureCollection();
data= data.Replace("M "," M").Replace("C "," C").Replace("L "," L");
string[] str = data.Split(' ');
for (int i = 0; i < str.Length; i++)
{
if (str[i].StartsWith("C") || str[i].StartsWith("c"))
{
string[] item = str[i].Split(',');
string[] item1 = str[i + 1].Split(',');
string[] item2 = str[i + 2].Split(',');
BezierSegment bs = new BezierSegment();
bs.Point1 = new Point(double.Parse(item[0].Substring(1)), double.Parse(item[1]));
bs.Point2 = new Point(double.Parse(item1[0]), double.Parse(item1[1]));
bs.Point3 = new Point(double.Parse(item2[0]), double.Parse(item2[1]));
i += 2;
psc.Add(bs);
}
else if (str[i].StartsWith("L") || str[i].StartsWith("l"))
{
string[] item = str[i].Split(',');
LineSegment ls = new LineSegment();
ls.Point = new Point(double.Parse(item[0].Substring(1)), double.Parse(item[1]));
psc.Add(ls);
}
else if (str[i].StartsWith("M") || str[i].StartsWith("m"))
{
string[] item = str[i].Split(',');
pf.StartPoint = new Point(double.Parse(item[0].Substring(1)), double.Parse(item[1]));
}
else if (str[i].StartsWith("z") || str[i].StartsWith("Z"))
{
pf.IsClosed = true;
}
}
pf.Segments = psc;
pfc.Add(pf);
pg.Figures = pfc;
return pg;
}
///// End of Method
Sample Code for calling method
Path path = new Path();
string str = " F1 M 933.291,430.505C 924.367,415.673 923.007,387.822 922.503,370.604C 921.343,331.31 944.994,317.76 975.999,296.994L 949.334,299.957C 938.729,302.545 930.572,309.925 920.255,313.368C 901.85,319.521 886.504,313.062 870.896,303.53C 850.12,290.842 831.457,270.65 815.107,251.462C 806.279,241.101 798.257,221.598 781.986,226.017C 767.327,229.99 760.199,246.869 743.058,244.012C 737.559,227.262 741.368,204.78 739.591,187.029C 738.108,172.136 733.986,158.933 733.996,143.736C 734.003,128.417 734.091,113.088 733.996,97.7689C 733.909,83.5475 730.302,82.6582 716.114,86.0475C 687.558,92.8796 663.68,115.232 634.418,119.337C 622.391,121.028 598.323,121.184 603.745,103.642C 603.745,103.642 547.667,116.478 522.623,101.969L 397.73,43.1915C 374.54,33.5875 352.799,21.5236 330.186,10.7568C 315.067,3.55951 298.84,3.50623 282.684,6.54358C 268.628,9.18353 252.14,8.36884 238.73,13.0222C 227.932,16.7648 225.711,27.0569 220.839,35.6369C 204.622,64.1582 184.474,89.9609 163.49,115.642C 143.3,140.356 124.747,161.949 100.268,182.977C 76.4618,203.437 58.0045,230.722 39.6698,256.062C 27.9845,272.228 10.5298,295.73 5.62447,315.546C 1.21381,333.368 7.65381,345.95 16.7778,360.225C 30.9738,382.42 52.4365,394.917 74.4578,408.658C 108.356,429.826 144.964,432.43 182.619,439.202C 194.226,441.284 201.93,444.466 212.456,450.234C 228.9,459.261 246.18,466.181 262.031,476.002C 277.378,485.518 288.175,498.328 306.771,498.502C 331.423,498.729 342.159,498.364 359.554,517.221C 368.632,527.06 372.859,537.585 380.38,548.114C 395.159,568.82 409.076,590.689 426.295,609.442C 440.326,624.728 467.967,633.601 487.652,636.902C 505.622,639.908 521.979,632.736 535.859,620.806C 545.402,612.606 552.478,602.246 557.978,591.161C 561.915,583.213 564.966,568.085 572.399,564.296C 578.046,561.41 595.117,563.91 601.338,564.312C 612.171,565.009 621.722,568.994 632.552,569.976C 651.071,571.65 654.679,567.992 668.187,558.989C 681.275,550.254 697.746,547.268 711.451,538.109C 733.726,523.208 751.861,501.273 773.035,484.254C 795.099,466.53 815.65,437.337 845.207,434.924C 871.813,432.754 933.291,430.505 933.291,430.505 Z";
path.Data = getPathGeometry(str);

Related

How do I crop an image per clipping path?

I have a TIFF with one clipping path stored in the 8BimProfile. Now I want to crop this image along the clipping path.
What I tried
My first approach was to use the MagickImage Clip() method, which seems to do nothing:
using (var image = new MagickImage(pathOfFileToClip))
{
image.Clip();
image.Write(targetPath);
}
The workaround I am currently using calls the ImageMagick convert.exe tool:
var wrappedFilePath = "\"" + pathOfFileToClip + "\"";
var arguments = wrappedFilePath + " -alpha transparent -clip -alpha opaque -strip " + wrappedFilePath;
var process = new System.Diagnostics.Process();
var startInfo = new System.Diagnostics.ProcessStartInfo
{
WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden,
FileName = #"C:\Program Files\ImageMagick-6.9.3-Q16\convert.exe",
Arguments = arguments
};
process.StartInfo = startInfo;
process.Start();
This works nicely and crops the image the way I want it. But how can I get this to work without the EXE?
Just taking the command line arguments like this did not work either:
using (var image = new MagickImage(pathOfFileToClip))
{
image.AlphaColor = new MagickColor(Color.Transparent);
image.Clip();
image.AlphaColor = new MagickColor(Color.Black);
image.Strip();
image.Write(targetPath);
}
Any suggestions or links to working solutions are appreciated.
So, turns out I was using the wrong method when applying the Alpha option:
using (var image = new MagickImage(pathOfFileToClip))
{
image.Alpha(AlphaOption.Transparent);
image.Clip();
image.Alpha(AlphaOption.Opaque);
image.Strip();
image.Write(targetPath);
}
I hope this still helps anybody who is trying this approach.

Display XPS Document in WPF

I need to display data from a database into a WPF app and save it as an XPS document. I want to display it as part of a main window (with Toolbar, Menu and StatusBar) and not as a new window.
What control(s) should I use? Currently, I am looking at FixedDocument and FlowDocument. Am I on the right track? Any good material on how to start?
Improving on Stehpen's answer above...
Assume you've added a documents folder to your project:
Create a method GetDocument where the GetFilePath method refers to the Folder/Filename in the folder above.
private void GetDocument()
{
string fileName = Environment.CurrentDirectory.GetFilePath("Documents\\Title.xps");
Debugger.Break();
XpsDocument doc = new XpsDocument(fileName, FileAccess.Read);
XDocViewer.Document = doc.GetFixedDocumentSequence();
}
Where GetFilePath is an extension method that looks like this:
public static class StringExtensions
{
public static string GetFilePath(
this string EnvironmentCurrentDirectory, string FolderAndFileName)
{
//Split on path characters
var CurrentDirectory =
EnvironmentCurrentDirectory
.Split("\\".ToArray())
.ToList();
//Get rid of bin/debug (last two folders)
var CurrentDirectoryNoBinDebugFolder =
CurrentDirectory
.Take(CurrentDirectory.Count() - 2)
.ToList();
//Convert list above to array for Join
var JoinableStringArray =
CurrentDirectoryNoBinDebugFolder.ToArray();
//Join and add folder filename passed in
var RejoinedString =
string.Join("\\", JoinableStringArray) + "\\";
var final = RejoinedString + FolderAndFileName;
return final;
}
}
Add a Document viewer in your XAML
And add this code in the cs file:
string fileName = null;
string appPath= System.IO.Path.GetDirectoryName(Assembly.GetAssembly(typeof(DocumentWindow)).CodeBase);
fileName = appPath + #"\Documents\Help.xps";
fileName = fileName.Remove(0, 6);
XpsDocument doc = new XpsDocument(fileName, FileAccess.Read);
docView.Document = doc.GetFixedDocumentSequence();

Excel doc contents to webservice

I have a wpf staff creation window in which I can create basic information like first name, last name etc this creates the staff in my REST web service. An example:
Client side:
private void CreateStaffMember_Click(object sender, RoutedEventArgs e)
{
string uri = "http://localhost:8001/Service/Staff";
StringBuilder sb = new StringBuilder();
sb.Append("<Staff>");
sb.AppendLine("<FirstName>" + this.textBox1.Text + "</FirstName>");
sb.AppendLine("<LastName>" + this.textBox2.Text + "</LastName>");
sb.AppendLine("<Password>" + this.passwordBox1.Password + "</Password>");
sb.AppendLine("</Staff>");
string NewStudent = sb.ToString();
byte[] arr = Encoding.UTF8.GetBytes(NewStudent);
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(uri);
req.Method = "POST";
req.ContentType = "application/xml";
req.ContentLength = arr.Length;
Stream reqStrm = req.GetRequestStream();
reqStrm.Write(arr, 0, arr.Length);
reqStrm.Close();
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
MessageBox.Show("Staff Creation: Status " + resp.StatusDescription);
reqStrm.Close();
resp.Close();
}
Web Service side:
#region POST
[OperationContract]
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Xml, ResponseFormat = WebMessageFormat.Xml, UriTemplate = "/Staff")]
void AddStaff(Staff staff);
#endregion
public void AddStaff(Staff staff)
{
staff.StaffID = (++eCount).ToString();
staff.Salt = GenerateSalt();
byte[] passwordHash = Hash(staff.Password, staff.Salt);
staff.Password = Convert.ToBase64String(passwordHash);
staffmembers.Add(staff);
}
All fine on that side, but Im looking to "import" the staff details from an excel spreadsheet, not sure if import is the correct word but I want to take the first names and last names contained in such n such spreadsheet and add them to the web service from the client side wpf application.
How would I go about it? I have my open file dialog:
private void Import_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
// Show open file dialog box
Nullable<bool> result = dlg.ShowDialog();
// Process open file dialog box results
if (result == true)
{
// Open document
string filename = dlg.FileName;
}
}
So I open my excel spread sheet then how would I go about taking the inner contents and sending it to the web service? Really stuck on the code or how to go about it :/
Just looking for an automated way of adding staff members rather than manually typing the names, but seeing as the staff excel doc could be named anything I wanted the open file dialog box. The structure inside will always be the same first name then last name.
First, here is my test Excel file that contains the Staff you want to import:
(Column 'A' if first name, column 'B' is last name and column 'C' is the password...)
Ok, so assuming that your code calling your web service works, here is my version of the Import_Click method (and a generic method to save new staff):
private void Import_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
// Show open file dialog box
Nullable<bool> result = dlg.ShowDialog();
// Process open file dialog box results
if (result == true)
{
// Open document
string filename = dlg.FileName;
Microsoft.Office.Interop.Excel.Application vExcelObj = new Microsoft.Office.Interop.Excel.Application();
try
{
Workbook theWorkbook = vExcelObj.Workbooks.Open(filename, Type.Missing, true);
Worksheet sheet = theWorkbook.Worksheets[1]; // This is assuming that the list of staff is in the first worksheet
string vFirstName = "temp";
string vLastName = "temp";
string vPassword = "temp";
int vIndex = 1;
while (vFirstName != "")
{
// Change the letters of the appropriate columns here!
// In my example, 'A' is first name, 'B' is last name and 'C' is the password
vFirstName = sheet.get_Range("A" + vIndex.ToString()).Value.ToString();
vLastName = sheet.get_Range("B" + vIndex.ToString()).Value.ToString();
vPassword = sheet.get_Range("C" + vIndex.ToString()).Value.ToString();
this.SaveNewStaff(vFirstName, vLastName, vPassword);
vIndex++;
}
}
catch (Exception ex)
{
MessageBox.Show("Error processing excel file : " + ex.Message);
}
finally {
vExcelObj.Quit();
}
}
}
private void SaveNewStaff(string firstName, string lastName, string password) {
string uri = "http://localhost:8001/Service/Staff";
StringBuilder sb = new StringBuilder();
sb.Append("<Staff>");
sb.AppendLine("<FirstName>" + firstName + "</FirstName>");
sb.AppendLine("<LastName>" + lastName + "</LastName>");
sb.AppendLine("<Password>" + password + "</Password>");
sb.AppendLine("</Staff>");
string NewStudent = sb.ToString();
byte[] arr = Encoding.UTF8.GetBytes(NewStudent);
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(uri);
req.Method = "POST";
req.ContentType = "application/xml";
req.ContentLength = arr.Length;
Stream reqStrm = req.GetRequestStream();
reqStrm.Write(arr, 0, arr.Length);
reqStrm.Close();
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
//MessageBox.Show("Staff Creation: Status " + resp.StatusDescription);
reqStrm.Close();
resp.Close();
}
Note: I have REMed out the MessageBox in the call to the web service to make sure you are not annoyed by it if the list is long, but you are free to "unREM" it if you need confirmation for every staff creation. In the same line of taught, there is not validation that the creation has occurred successfully. I would need more details to create a decent validation process.
Also VERY important, this does not validate if the staff you are saving already exists in the list. If you re-run this import procedure multiple times, it may (and probably will) create duplicate entries.
Cheers

Silverlight 4 chart created at runtime won't show data

Using the Silverlight 4 toolkit chart control, I am trying to create a chart 100% at runtime with no evidence of it anywhere in the XAML. To do so, I create the blank chart when the page loads:
Chart TrendChart = new Chart();
TrendChart.Name = "TrendChart";
TrendChart.Title = "Call History";
TrendChart.HorizontalContentAlignment = System.Windows.HorizontalAlignment.Stretch;
TrendChart.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
TrendChart.VerticalAlignment = System.Windows.VerticalAlignment.Stretch;
TrendChart.VerticalContentAlignment = System.Windows.VerticalAlignment.Stretch;
GridPanel.Children.Add(TrendChart);
After the user clicks on a button to retrieve data, a List is created of this custom class:
private class PhoneTrendDataPoint
{
public string XValue { get; set; }
public double YValue { get; set; }
}
I use that List, called CurrentCallTrends, as an ItemsSource for my chart.
// Update the chart with the received data
Chart TrendChart = (Chart)this.FindName("TrendChart");
// Wipe out previous chart data
TrendChart.Series.Clear();
// set the data
ColumnSeries columnSeries = new ColumnSeries();
columnSeries.Name = "Current Call Volume";
columnSeries.ItemsSource = CurrentCallTrends;
//columnSeries.SetBinding(ColumnSeries.ItemsSourceProperty, new Binding("CurrentCallTrends"));
columnSeries.DependentValueBinding = new Binding("XValue");
columnSeries.IndependentValueBinding = new Binding("YValue");
TrendChart.Series.Add(columnSeries);
The problem is that I get a runtime error where it prompts me to open a debugger regarding an object reference not set to an instance of an object. If I comment the line to .SetBinding then the ItemsSource vanishes and no data shows up, but at least there is no runtime error.
What am I missing?
After additional Googling, I made some modifications that seem to work but don't strike me as the best way to go about doing this. Data now shows up, but I will not accept this as the answer unless there is no better method:
// Update the chart with the received data
Chart TrendChart = (Chart)this.FindName("TrendChart");
// Wipe out previous chart data
TrendChart.Series.Clear();
// test data
KeyValuePair<string, double>[] CurrentCallData = new KeyValuePair<string, double>[CurrentCallTrends.Count];
for (int i = 0; i < CurrentCallTrends.Count; i++)
{
CurrentCallData[i] = new KeyValuePair<string, double>(CurrentCallTrends[i].XValue, CurrentCallTrends[i].YValue);
}
// set the data
ColumnSeries columnSeries = new ColumnSeries();
columnSeries.Name = "CurrentCallVolume";
columnSeries.Title = "Current Call Volume";
columnSeries.SetBinding(ColumnSeries.ItemsSourceProperty, new Binding());
//columnSeries.ItemsSource = CurrentCallTrends;
columnSeries.ItemsSource = CurrentCallData;
columnSeries.DependentValueBinding = new Binding("Value");
columnSeries.IndependentValueBinding = new Binding("Key");
TrendChart.Series.Add(columnSeries);
//this.DataContext = CurrentCallTrends;

Silverlight XamlWriter

I see that the .Net XamlWriter is not available in Silverlight. Well - I need one anyway, so I assume there is a solution to this..?
I have some UIElement objects (Path, Ellipse, Rectangle, ..), and I want to store their Xaml definition such that I can load these later using XamlWriter.Load(). Any ideas on how to do this? Any 3rdParty XamlWriter implementations etc that are recommended?
There seems to be some implementations of XamlWriter for Silverlight around. The one I've seen which looks most serious is in Silverlight Contrib, but this is not yet supported for SL3, which I'm using.
Since I only had a few specific objects to extract xaml from I created the functions to do so myself. Some more refactoring will be done, but this one works for finding the xaml for my path drawing - stored as a InkPresenter:
public static string ConvertPathToXaml(InkPresenter drawObject)
{
string xmlnsString = "http://schemas.microsoft.com/client/2007";
XNamespace xmlns = xmlnsString;
var strokes = new XElement(xmlns + "StrokeCollection");
foreach (var strokeData in drawObject.Strokes)
{
var stroke = new XElement(xmlns + "Stroke",
new XElement(xmlns + "Stroke.DrawingAttributes",
new XElement(xmlns + "DrawingAttributes",
new XAttribute("Color", strokeData.DrawingAttributes.Color),
new XAttribute("OutlineColor", strokeData.DrawingAttributes.OutlineColor),
new XAttribute("Width", strokeData.DrawingAttributes.Width),
new XAttribute("Height", strokeData.DrawingAttributes.Height))));
var points = new XElement(xmlns + "Stroke.StylusPoints");
foreach (var pointData in strokeData.StylusPoints)
{
var point = new XElement(xmlns + "StylusPoint",
new XAttribute("X", pointData.X),
new XAttribute("Y", pointData.Y));
points.Add(point);
}
stroke.Add(points);
strokes.Add(stroke);
}
var strokesRoot = new XElement(xmlns + "InkPresenter.Strokes", strokes);
var inkRoot = new XElement(xmlns + "InkPresenter", new XAttribute("xmlns", xmlnsString),
new XAttribute("Opacity", drawObject.Opacity), strokesRoot);
return inkRoot.ToString();
}

Resources