How to convert ContentControl into PlainTextContentControl/access the .Text property? - c#

I'm trying to programatically fill in some ContentControls inside a MS Word document with C#.
So far I've been able to open the document and find all the controls, but they come back as generic ContentControl objects. Inspecting them with a debugger just reveals a generic System.__ComObject.
From the docs I can see that some of the controls should have a .Text property, but I cannot figure out how to access it.
I can determine the type of the control using the switch statement you see below, but it doesn't really help me -- I don't know what class to cast the object to (if that's even what I'm supposed to do).
There is a class called PlainTextContentControl but it exists in Microsoft.Office.Tools.Word, but the Application and Document and ContentControl live under Microsoft.Office.Interop.Word and these do not play nicely together.
So I'm lost. How do I access the Text property? Here's what I've got:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Word;
//using Microsoft.Office.Interop.Word;
using Microsoft.Office.Tools.Word;
using ContentControl = Microsoft.Office.Interop.Word.ContentControl;
using Document = Microsoft.Office.Interop.Word.Document;
namespace ConsoleApplication1
{
internal class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Opening Word Application...");
var app = new Application();
try
{
Console.WriteLine("Loading document...");
var doc = app.Documents.Open(
#"C:\blahblah\template3.docx");
Console.WriteLine("Finding controls...");
var controls = GetAllContentControls(doc);
foreach (var control in controls)
{
Console.WriteLine(control.Tag);
switch (control.Type)
{
case WdContentControlType.wdContentControlText:
var pt = control as PlainTextContentControl;
Console.WriteLine("hit"); // pt is null
break;
}
}
doc.Close();
}
finally
{
app.Quit();
}
}
public static List<ContentControl> GetAllContentControls(Document wordDocument)
{
var ccList = new List<ContentControl>();
foreach (Range range in wordDocument.StoryRanges)
{
var rangeStory = range;
do
{
try
{
foreach (ContentControl cc in rangeStory.ContentControls)
{
ccList.Add(cc);
}
}
catch (COMException)
{
}
rangeStory = rangeStory.NextStoryRange;
} while (rangeStory != null);
}
return ccList;
}
}
}
I should note that I'm using JetBrains Rider instead of Visual Studio. If this is impossible to do with Rider for some reason, I can probably obtain a copy of VS.

You can just use the code resemble the following:
switch (control.Type)
{
case WdContentControlType.wdContentControlText:
var text = control.Range.Text;
//var pt = control as PlainTextContentControl;// pt is null
Console.WriteLine(text);
break;
case WdContentControlType.wdContentControlRichText:
var richText = control.Range.Text;
//var pt1 = control as PlainTextContentControl;// pt1 is null
Console.WriteLine(richText);
break;
}

Related

How to use CommandLineParser in a WinForms Project? How to build a custom Help MessageBox?

I'm trying to use the CommandLineParser Library in Version 2.5.0 in a WinForms application.
It works great except for a help screen (MessageBox in that case).
I already figured out that I need to create a own parser and set at least the HelpWriter property to null to create a custom Help Screen.
But when the application is called with --help argument my "Error handler" just get one error instance with a Tag of type CommandLine.ErrorType and a Value of HelpRequestedError
Now how to build the custom Help Screen?
https://github.com/commandlineparser/commandline/wiki/Generating-Help-and-Usage-information
This site suggests to use the Types in CommandLine.Text Namespace but how? There are zero examples how to do it.
Anyone here did something like this?
I have the following code:
namespace myWorkspace
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows.Forms;
using CommandLine;
using DevExpress.XtraEditors;
using Options;
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
internal static int Main(string[] args)
{
AppDomain.CurrentDomain.SetupInformation.PrivateBinPath = "bin";
WindowsFormsSettings.EnableFormSkins();
WindowsFormsSettings.EnableMdiFormSkins();
WindowsFormsSettings.ForceDirectXPaint();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var parser = new Parser(config =>
{
config.AutoHelp = true;
config.AutoVersion = true;
config.CaseInsensitiveEnumValues = false;
config.CaseSensitive = false;
config.EnableDashDash = true;
config.HelpWriter = null;
config.IgnoreUnknownArguments = true;
//config.MaximumDisplayWidth
config.ParsingCulture = CultureInfo.InvariantCulture;
});
return Parser.Default.ParseArguments<RunOptions>(args)
.MapResult(
RunRunAndReturnExitCode,
RunParsingFailedAndReturnExitCode);
}
private static int RunRunAndReturnExitCode(RunOptions opts)
{
try
{
Application.Run(new MainForm());
}
catch
{
return -1;
}
return 0;
}
private static int RunParsingFailedAndReturnExitCode(IEnumerable<Error> errs)
{
foreach (var err in errs)
{
var locErr = err;
}
return 1;
}
}
}
And on Line var locErr = err; i don't know what to do to get a help screen message i can show in a MessageBox or the like.
CommandLineParser seems to support console output out-of-the-box for help or --help but I have no console app here.
Ok i now figured out a way to do it. Does not seem to be the best way but it works.
I create a StringBuilder instance and put it into a StringWriter instance
private static StringBuilder helpTextBuilder = new StringBuilder();
private static StringWriter helpTextWriter = new StringWriter(helpTextBuilder);
Then I create a new Parser with (at least this) Option(s):
var parser = new Parser(config =>
{
config.HelpWriter = helpTextWriter;
});
In the case of error I can now use what is written into the helpTextBuilder to show a message box.
private static int RunParsingFailedAndReturnExitCode(IEnumerable<Error> errs)
{
MessageBox.Show(helpTextBuilder.ToString());
return 1;
}
So this is now working for me.

CRM Plugin will not update or do anything to change a fields value

So I have written a plugin to do some simple calculations and and update fields based upon certain conditions. The plugin compiles and doesn't cause any errors while profiling or create any instances where I can debug my code which is frustrating. Anyways without further ado here it is:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BPT.PluginCommon.BaseClasses;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Xrm;
namespace Engage.Crm.Plugins
{
public class VoidPayment : BPTPluginBase
{
bpt_DuesHeader oDuesHeader = new bpt_DuesHeader();
Account org = new Account();
public override void HandleAfterOp()
{
try
{
base.HandleAfterOp();
var crmContext = new XrmServiceContext(this.OrganizationService);
if (this.PluginExecutionContext.MessageName == MessageName.Create ||
this.PluginExecutionContext.MessageName == MessageName.Update)
{
if (this.InputTargetEntity.Attributes.Contains("gih_void"))
{
var Void = (bool) this.InputTargetEntity.Attributes["gih_void"];
var voidReason = (OptionSetValue) this.InputTargetEntity.Attributes["gih_voidreason"];
var totalPayments = (Money) this.InputTargetEntity.Attributes["bpt_TotalPayments"];
var amountBilled =
crmContext.bpt_DuesHeaderSet.Where(
o => o.bpt_DuesHeaderId == this.PluginExecutionContext.PrimaryEntityId)
.ToList()
.Sum(o => o.bpt_TotalAmountBilled == null ? 0 : o.bpt_TotalAmountBilled.Value);
if (Void)
{
this.oDuesHeader.bpt_TotalAdjustments = new Money(amountBilled);
this.oDuesHeader.bpt_TotalAmountBilled =
new Money(oDuesHeader.bpt_TotalAdjustments.Value + totalPayments.Value);
this.oDuesHeader.bpt_Balance = new Money(amountBilled);
if (voidReason.Value == 914020000)
//should be dropped not default option
{
oDuesHeader.gih_terminationdate = DateTime.Now;
}
}
OrganizationService.Update(oDuesHeader);
}
}
}
catch (Exception ex)
{
this.TracingService.Trace(this.ToString() + " {0}", "Exception: {0}", ex.ToString());
throw;
}
}
}
}
Sorry code is not formating well! Help! The plugin is registered as post-operation and synchronous. Any insight would be helpful and if a moderator could help format the code that would be greatly appreciated because it is not letting me add four spaces in certain places.
Plugins in CRM are created once, and then used multiple time, maybe even simultaneously, so besides setting the Id, don't use class level fields. You're creating a race condition that could really do some unwanted changes.

in C# replace text with an image at the same position

This code is replacing the text with an image but its placing the multiple copies of an image and placing them in the beginning of the document. I want the image to be placed at the same position where text was present. My find text is available in the table cell. Is it due to that?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.IO;
using word = Microsoft.Office.Interop.Word;
using System.Runtime.InteropServices;
//using System.Drawing;
namespace WritingIntoDocx
{
[ComVisible(true)]
public interface IMyClass
{
void DocumentDigitalSign(string filep,string findt,string replacet);
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class Program : IMyClass
{
public void DocumentDigitalSign(string filep, string findt, string imagepath)
{
string filepath = filep;
string Findtext = findt;
word.Application app = new word.Application();
word.Document doc = app.Documents.Open(filepath);
word.Range myStoryRange = doc.Range();
//First search the main document using the Selection
word.Find myFind = myStoryRange.Find;
myFind.Text = Findtext; myFind.Replacement.Application.Selection.InlineShapes.AddPicture(imagepath);
myFind.Forward = true;
myFind.Wrap = word.WdFindWrap.wdFindContinue;
myFind.Format = false;
myFind.MatchCase = false;
myFind.MatchWholeWord = false;
myFind.MatchWildcards = false;
myFind.MatchSoundsLike = false;
myFind.MatchAllWordForms = false;
myFind.Execute(Replace: word.WdReplace.wdReplaceAll);
//'Now search all other stories using Ranges
foreach (word.Range otherStoryRange in doc.StoryRanges)
{
if (otherStoryRange.StoryType != word.WdStoryType.wdMainTextStory)
{
word.Find myOtherFind = otherStoryRange.Find;
myOtherFind.Text = Findtext; myOtherFind.Replacement.Application.Selection.InlineShapes.AddPicture(imagepath);
myOtherFind.Wrap = word.WdFindWrap.wdFindContinue;
myOtherFind.Execute(Replace: word.WdReplace.wdReplaceAll);
}
// 'Now search all next stories of other stories (doc.storyRanges dont seem to cascades in sub story)
word.Range nextStoryRange = otherStoryRange.NextStoryRange;
while (nextStoryRange != null)
{
word.Find myNextStoryFind = nextStoryRange.Find;
myNextStoryFind.Text = Findtext;
myNextStoryFind.Replacement.Application.Selection.InlineShapes.AddPicture(imagepath);
myNextStoryFind.Wrap = word.WdFindWrap.wdFindContinue;
myNextStoryFind.Execute(Replace: word.WdReplace.wdReplaceAll);
nextStoryRange = nextStoryRange.NextStoryRange;
}
}
app.Documents.Save();
app.Documents.Close();
}
}
}
Replacement.Application is a reference to the application object. When you call AddPicture() on that, the picture is immediately inserted at the current position before the find operation is even executed.
I see two possibilities:
Load the picture, place it into the Windows clipboard and then execute the find operation specifying "^c" as replacement text. Word will replace "^c" with the current content of the clipboard. This is what the documentation says:
ReplaceWith
Type: System.Object
Optional Object.
The replacement text. To delete the text specified by the Find argument, use an empty string (""). You specify special characters and advanced search criteria just as you do for the Find argument. To specify a graphic object or other non-text item as the replacement, move the item to the Clipboard and specify "^c" for ReplaceWith.
Do not use wdReplaceAll, but wdReplaceNone, so that the find operation itself does not do any replacing. But you then have the chance to insert your content at the place found. Do that in a loop until no more occurrence is found.

Getting a SemanticModel of a cshtml file?

I'd like to use Roslyn to analyze semantic information within the context of a block of C# code inside a Razor View.
Is there any way (within Visual Studio 2015, or even in a unit test) to get the SemanticModel that represents this code?
Razor files contain a C# projection buffer with the generated C# code (including the parts that you don't write yourself). This buffer has full Roslyn services and is exactly what you're looking for.
You need to walk through the TextView's BufferGraph and find the CSharp buffer; you can then get its Document and semantic model.
If you're starting from the cursor location, you need simply need to map that location to a CSharp buffer.
Note that it is perfectly legal for a TextView to contain multiple CSharp buffers. (although the Razor editor will never do that)
If you aren't working in a TextView, you need to do all of this yourself; you need to run the Razor source through the Razor compiler to get the generated C# source, then compile that with Roslyn to get a semantic model.
Extract the code representing the view from the Razor view file using RazorTemplateEngine.GenerateCode and CSharpCodeProvider.GenerateCodeFromCompileUnit (or the VBCodeProvider if you want the intermediate source as VB.NET). You can then use Roslyn to parse the code.
There's an example of using Roslyn with Razor view files here.
Take note that GenerateCode carries a caveat:
This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.
Just in case anyone else gets stuck on this, I have mini sample app which may help.
I had a CMS class like this:
public partial class CMS
{
public static string SomeKey
{
get { return (string) ResourceProvider.GetResource("some_key"); }
}
// ... and many more ...
}
... and I wanted to find out which of these were used throughout my solution for a report ... Enter Roslyn!
The following app will print out the count for the used and unused references:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.MSBuild;
using Microsoft.CSharp;
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Razor;
namespace TranslationSniffer
{
class Program
{
static void Main(string[] args)
{
new Program().Go().Wait();
}
public async Task Go()
{
// Roslyn!
var ws = MSBuildWorkspace.Create();
// Store the translation keys...
List<string> used = new List<string>();
List<string> delete = new List<string>();
string solutionRoot = #"C:\_Code\PathToProject\";
string sln = solutionRoot + "MySolution.sln";
// Load the solution, and find all the cshtml Razor views...
var solution = await ws.OpenSolutionAsync(sln);
var mainProj = solution.Projects.Where(x => x.Name == "ConsumerWeb").Single();
FileInfo[] cshtmls = new DirectoryInfo(solutionRoot).GetFiles("*.cshtml", SearchOption.AllDirectories);
// Go through each Razor View - generate the equivalent CS and add to the project for compilation.
var host = new RazorEngineHost(RazorCodeLanguage.Languages["cshtml"]);
var razor = new RazorTemplateEngine(host);
var cs = new CSharpCodeProvider();
var csOptions = new CodeGeneratorOptions();
foreach (var cshtml in cshtmls)
{
using (StreamReader re = new StreamReader(cshtml.FullName))
{
try
{
// Let Razor do it's thang...
var compileUnit = razor.GenerateCode(re).GeneratedCode;
// Pull the code into a stringbuilder, and append to the main project:
StringBuilder sb = new StringBuilder();
using (StringWriter rw = new StringWriter(sb))
{
cs.GenerateCodeFromCompileUnit(compileUnit, rw, csOptions);
}
// Get the new immutable project
var doc = mainProj.AddDocument(cshtml.Name + ".cs", sb.ToString());
mainProj = doc.Project;
}
catch(Exception ex)
{
Console.WriteLine("Compile fail for: {0}", cshtml.Name);
// throw;
}
continue;
}
}
// We now have a new immutable solution, as we have changed the project instance...
solution = mainProj.Solution;
// Pull out our application translation list (its in a static class called 'CMS'):
var mainCompile = await mainProj.GetCompilationAsync();
var mainModel = mainCompile.GetTypeByMetadataName("Resources.CMS");
var translations = mainModel.GetMembers().Where(x => x.Kind == SymbolKind.Property).ToList();
foreach (var translation in translations)
{
var references = await SymbolFinder.FindReferencesAsync(translation, solution) ;
if (!references.First().Locations.Any())
{
Console.WriteLine("{0} translation is not used!", translation.Name);
delete.Add(translation.Name);
}
else
{
Console.WriteLine("{0} :in: {1}", translation.Name, references.First().Locations.First().Document.Name);
used.Add(translation.Name);
}
}
Console.WriteLine();
Console.WriteLine("Used references {0}. Unused references: {1}", used.Count, delete.Count);
return;
}
}
}
Roslyn only models cshtml files while they are open, but during that time they are similar to every other source file in the Workspace model.
Is there something specific you have tried that isn't working?

Edit eastAsia attribute in OpenXML

I have an issue with our firm's Normal.dotm related to the eastAsia attribute in styles.xml. If you're interested, you can find a history of the issue here. We can't just replace the template firmwide without overwriting custom styles/macros, etc. I have almost no experience with OpenXML, but I thought it might hold a solution to the problem. However, all the articles and tutorials I've found haven't been much help. They all reference the "Document" part and are focused on changing content rather than elements and attributes.
Basically, I need to loop through every <w:rFonts> element and change the w:eastAsia attribute from "Times New Roman" to "MS Mincho." This is the only part I feel confident about:
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Packaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace eastAsiaFix
{
class Program
{
static void Main(string[] args)
{
using (WordprocessingDocument myDocument = WordprocessingDocument.Open(#"C:\users\" + Environment.UserName + #"\Desktop\eastAsiaFix.dotm", true))
{
StyleDefinitionsPart styles = myDocument.MainDocumentPart.StyleDefinitionsPart;
if (styles == null)
{
return;
}
}
}
}
}
I think what I need is something like the following:
foreach (OpenXMLElement theStyle in styles.Styles.ChildElements)
{
if (theStyle.LocalName = "style")
{
theStyle.StyleRunProperties.RunFonts.EastAsia.Value = "MS Mincho"; //faking this
}
}
How do I get to the w:rFonts node and edit the eastAsia attribute?
I can think of two different solutions to change the East Asian font value.
The first solution just changes the East Asian font value for all RunFonts
under the Styles collection. This solutions would also change the East Asian
font value for the document default paragraph and run properties (DocDefaults class, w:docDefaults).
using (WordprocessingDocument myDocument = WordprocessingDocument.Open(#"C:\users\" + Environment.UserName + #"\Desktop\eastAsiaFix.dotm", true))
{
StyleDefinitionsPart stylesPart = myDocument.MainDocumentPart.StyleDefinitionsPart;
if (stylesPart == null)
{
Console.Out.WriteLine("No styles part found.");
return;
}
foreach(var rf in stylesPart.Styles.Descendants<RunFonts>())
{
if(rf.EastAsia != null)
{
Console.Out.WriteLine("Found: {0}", rf.EastAsia.Value);
rf.EastAsia.Value = "MS Mincho";
}
}
}
The second solution would be to change the East Asian font value
only for the style definitions (and not for the document default paragraph and run properties):
using (WordprocessingDocument myDocument = WordprocessingDocument.Open(#"C:\users\" + Environment.UserName + #"\Desktop\eastAsiaFix.dotm", true))
{
StyleDefinitionsPart stylesPart = myDocument.MainDocumentPart.StyleDefinitionsPart;
if (stylesPart == null)
{
Console.Out.WriteLine("No styles part found.");
return;
}
foreach(var style in stylesPart.Styles.Descendants<Style>())
{
foreach(var rf in style.Descendants<RunFonts>())
{
if(rf.EastAsia != null)
{
Console.Out.WriteLine("Found: {0}", rf.EastAsia.Value);
rf.EastAsia.Value = "MS Mincho";
}
}
}
}

Categories

Resources