How to programmatically read and change slide notes in PowerPoint - c#

How do you get the Notes text from the current PowerPoint slide using C#?

I believe this might be what you are looking for:
string s = slide.NotesPage.Shapes[2].TextFrame.TextRange.Text
slide.NotesPage.Shapes[2].TextFrame.TextRange.Text = "Hello World"

Here is my code that I use for getting the slide notes. Still developing it, but seems to do the trick for the time being. Even in my simple test PPT the slide notes are not always the [2] element in the shapes array, so it is important to check.
private string GetNotes(Slide slide)
{
if (slide.HasNotesPage == MsoTriState.msoFalse)
return string.Empty;
string slideNodes = string.Empty;
var notesPage = slide.NotesPage;
int length = 0;
foreach (Shape shape in notesPage.Shapes)
{
if (shape.Type == MsoShapeType.msoPlaceholder)
{
var tf = shape.TextFrame;
try
{
//Some TextFrames do not have a range
var range = tf.TextRange;
if (range.Length > length)
{ //Some have a digit in the text,
//so find the longest text item and return that
slideNodes = range.Text;
length = range.Length;
}
Marshal.ReleaseComObject(range);
}
catch (Exception)
{}
finally
{ //Ensure clear up
Marshal.ReleaseComObject(tf);
}
}
Marshal.ReleaseComObject(shape);
}
return slideNodes;
}

Related

Highlighting words in RichEditBox

It is needed to highlight by fiven colour a substring in the document RichEditBox. For this purpose I wrote a method:
private async Task ChangeTextColor(string text, Color color)
{
string textStr;
bool theEnd = false;
int startTextPos = 0;
myRichEdit.Document.GetText(TextGetOptions.None, out textStr);
while (theEnd == false)
{
myRichEdit.Document.GetRange(startTextPos, textStr.Length).GetText(TextGetOptions.None, out textStr);
var isFinded = myRichEdit.Document.GetRange(startTextPos, textStr.Length).FindText(text, textStr.Length, FindOptions.None);
if (isFinded != 0)
{
string textStr2;
textStr2 = myRichEdit.Document.Selection.Text;
var dialog = new MessageDialog(textStr2);
await dialog.ShowAsync();
myRichEdit.Document.Selection.CharacterFormat.BackgroundColor = color;
startTextPos = myRichEdit.Document.Selection.EndPosition;
myRichEdit.Document.ApplyDisplayUpdates();
}
else
{
theEnd = true;
}
}
}
In the debugger you can see that there is a substring and isFinded is equal with the number of signs (or symbols) in the found substring. It means the fragment is found and judging by the description of the method FindText should be highlighted but it isn't. In textStr2 an empty line returns and, correspondingly, the colour doesn't change. I cannot identify the reasons of the error.
The code you postted did not set the selection, so the myRichEdit.Document.Selection is null. You can use ITextRange.SetRange to set the selection. And you can use ITextRange.FindText method to find the string in the selection.
For example:
private void ChangeTextColor(string text, Color color)
{
string textStr;
myRichEdit.Document.GetText(TextGetOptions.None, out textStr);
var myRichEditLength = textStr.Length;
myRichEdit.Document.Selection.SetRange(0, myRichEditLength);
int i = 1;
while (i > 0)
{
i = myRichEdit.Document.Selection.FindText(text, myRichEditLength, FindOptions.Case);
ITextSelection selectedText = myRichEdit.Document.Selection;
if (selectedText != null)
{
selectedText.CharacterFormat.BackgroundColor = color;
}
}
}

LINQ instead of for loop in wpf?

I am working with the WPF RichTextbox. I have to get all the lines in the RichTextbox. So I am using a for loop to get all the lines, but the RichTextbox contains a large text content. It takes too much time.
So how do I get around a 1000 line loop in less time?
I've tried parallel.for but it gives an exception as it tries to get each line of the RichTextbox text.
Here is my code.
for (Int32 icnt = 0; icnt <= iLineCount; icnt++)
{
LineDetails lnDtls = new LineDetails();
lnDtls.LineText = GetLineText(txtAppendValue.CaretPosition.GetLineStartPosition(icnt));
iCurrentEnd = iCurrentEnd + lnDtls.LineText.Length;
lnDtls.LineLength = iCurrentEnd;
listLines.Add(lnDtls);
}
GetLineText():
String GetLineText(TextPointer TextPointer)
{
tp1 = TextPointer.GetLineStartPosition(0);
if (tp1 == null)
{
return null;
}
else
{
tpNextLine2 = tp1.GetLineStartPosition(1);
if (tr != null)
{
tr = null;
}
if (tpNextLine2 == null)
{
tpNextLine2 = txtAppendValue.Document.ContentEnd;
}
tr = new TextRange(tp1, tpNextLine2);
return tr.Text;
}
}
So can I use LINQ instead of for loop for fast execution?
I don't see why it needs to be this complicated. A few simple line of code would give you all the lines in a rich text box.
string text = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd).Text;
text.Replace("\r\n", "\n");
var lines = text.Split(new char[] {'\n'});

C# RichTextBox Line-by-line scan

Hey Im creating my own coding language and I already have the entire application set up with save, open, close, new, etc.
In order for the "Run" part to work, I need a way to scan and test every single line in richTextBox1.
Maybe for past Java users, something along the lines of the "java-util-scanner," but easier to use for testing each line.
Does anyone know a way to do this, where a string "scannedString" would be tested as so:
if(scannedString == "#load(moduleFiles)") {
//action here
}
string scannedStringNextLine = ???
if(scannedStringNextLine = "") {
//action here
}
Eventually it would look more like this:
if(scannedString == "code1" || scannedString == "code2" etc... ) {
if(scannedString == "code1") {
//action here
}
} else {
//error message
}
hope this is enough information...
To get lines of code of the RichTextBox you can split the content by a new line symbol:
var lines = this.richTextBox.Text.Split('\n').ToList();
The lines are in order of appearance. Then you can go through lines in a for or foreach loop:
foreach (var line in lines)
{
// access a line, evaluate it, etc.
}
One way to do it is to split the text on the newline charaters and then parse each line for the text you care about:
private void btnScan_Click(object sender, EventArgs e)
{
var code = richTextBox1.Text;
foreach (var line in code.Split(new []{'\n','\r'},
StringSplitOptions.RemoveEmptyEntries))
{
CheckForLoad(line);
}
}
void CheckForLoad(string line)
{
if (string.IsNullOrWhiteSpace(line)) return;
int i = line.IndexOf("#load");
if (i < 0) return;
int openParen = line.IndexOf("(", i + 1);
if (openParen < 0) return;
int closeParen = line.IndexOf(")", openParen + 1);
if (closeParen < 0) return;
string modules = line.Substring(openParen + 1, closeParen - openParen - 1);
MessageBox.Show(string.Format("Loading modules: {0}", modules));
}

WPF Flowdocument "change case" feature

I am implementing a "change case" functionality for my RichTextBox like word has with Shift+F3. All it does is switching between lower->upper->title case, which is very simple once I get access to the string I need.
My question is, how to change (and find it in the first place) a string in flowdocument without losing any embedded elements (losing formatting is not a problem) that may be contained within the string.
Same as word, I need this functionality for 2 cases:
1) Mouse-selected text. I tried simply
this.Selection.Text = newText;
But that of course lost my embedded elements.
2) The last word before caret position. Any non-text element is a word delimiter, however one word can be
"He<weird formatting begin>ll<weird formatting end>o".
SOLUTION
This way it mimics MS WORD Shift+F3 behaviour. Only problem that in very few cases occurs is the carret being moved to the word beginning instead of keeping its position. I suppose that a short sleep after EditingCommands.MoveLeftByWord.Execute(null, this); would fix this, but this would be a dirty hack and I am trying to find out a nicer solution.
private void ChangeCase()
{
try
{
TextPointer start;
TextPointer end;
FindSelectedRange(out start, out end);
List<TextRange> textToChange = SplitToTextRanges(start, end);
ChangeCaseToAllRanges(textToChange);
}
catch (Exception ex)
{
mLog.Error("Change case error", ex);
}
}
private void FindSelectedRange(out TextPointer start, out TextPointer end)
{
if (!this.Selection.IsEmpty)
{
start = this.Selection.Start;
end = this.Selection.End;
}
else
{
end = this.CaretPosition;
EditingCommands.MoveLeftByWord.Execute(null, this);
start = this.CaretPosition;
this.CaretPosition = end;
}
}
private static List<TextRange> SplitToTextRanges(TextPointer start, TextPointer end)
{
List<TextRange> textToChange = new List<TextRange>();
var previousPointer = start;
for (var pointer = start; pointer.CompareTo(end) <= 0; pointer = pointer.GetPositionAtOffset(1, LogicalDirection.Forward))
{
var contextAfter = pointer.GetPointerContext(LogicalDirection.Forward);
var contextBefore = pointer.GetPointerContext(LogicalDirection.Backward);
if (contextBefore != TextPointerContext.Text && contextAfter == TextPointerContext.Text)
{
previousPointer = pointer;
}
if (contextBefore == TextPointerContext.Text && contextAfter != TextPointerContext.Text && previousPointer != pointer)
{
textToChange.Add(new TextRange(previousPointer, pointer));
previousPointer = null;
}
}
textToChange.Add(new TextRange(previousPointer ?? end, end));
return textToChange;
}
private void ChangeCaseToAllRanges(List<TextRange> textToChange)
{
var textInfo = (mCasingCulture ?? CultureInfo.CurrentUICulture).TextInfo;
var allText = String.Join(" ", textToChange.Select(x => x.Text).Where(x => !string.IsNullOrWhiteSpace(x)));
Func<string, string> caseChanger = GetConvertorToNextState(textInfo, allText);
foreach (var range in textToChange)
{
if (!range.IsEmpty && !string.IsNullOrWhiteSpace(range.Text))
{
range.Text = caseChanger(range.Text);
}
}
}
private static Func<string, string> GetConvertorToNextState(TextInfo textInfo, string allText)
{
Func<string, string> caseChanger;
if (textInfo.ToLower(allText) == allText)
{
caseChanger = (text) => textInfo.ToTitleCase(text);
}
else if (textInfo.ToTitleCase(textInfo.ToLower(allText)) == allText)
{
caseChanger = (text) => textInfo.ToUpper(text);
}
else
{
caseChanger = (text) => textInfo.ToLower(text);
}
return caseChanger;
}

MS Chart Control series label positioning

I've got a gantt chart (RangeBar) I've made with the MS Chart control; for some shorter series, the label gets displayed outside the bar; I'd prefer to set it so the label gets stays inside the bar and gets truncated (with ellipsis would be nice). Is there a way of doing this? I've been poking around in the properties of the chart and series for ages now with no success.
I think the property you need to set is BarLabelStyle
eg.
chart.Series["mySeries"]["BarLabelStyle"] = "Center";
See this Dundas page that explains that custom property which should be similar or same for the MS Chart control.
In the end I rolled my own using this (yes it's messy, it'll get tidied up when I have time):
private static void Chart_PostPaint(object sender, ChartPaintEventArgs e)
{
Chart c = ((Chart)sender);
foreach (Series s in c.Series)
{
string sVt = s.GetCustomProperty("PixelPointWidth");
IGanttable ig = (IGanttable)s.Tag;
double dblPixelWidth = c.ChartAreas[0].AxisY.ValueToPixelPosition(s.Points[0].YValues[1]) - c.ChartAreas[0].AxisY.ValueToPixelPosition(s.Points[0].YValues[0]);
s.Label = ig.Text.AutoEllipsis(s.Font, Convert.ToInt32(dblPixelWidth)-dblSeriesPaddingGuess);
}
}
public static string AutoEllipsis(this String s, Font f, int intPixelWidth)
{
if (s.Length == 0 || intPixelWidth == 0) return "";
var result = Regex.Split(s, "\r\n|\r|\n");
List<string> l = new List<string>();
foreach(string str in result)
{
int vt = TextRenderer.MeasureText(str, f).Width;
if (vt < intPixelWidth)
{ l.Add(str); }
else
{
string strTemp = str;
int i = str.Length;
while (TextRenderer.MeasureText(strTemp + "…", f).Width > intPixelWidth)
{
strTemp = str.Substring(0, --i);
if (i == 0) break;
}
l.Add(strTemp + "…");
}
}
return String.Join("\r\n", l);
}
This seems to work quite happily as long as it is the Post_Paint event (If you use the Paint event it will stop ToolTips from showing)

Categories

Resources