Hello fellow developers,
Being a newbie with the OpenXML SDK I cannot figure out how to retrieve a graph part that I've put in a rich text control (with a specific tag name).
For the moment I retrieve the graph part by using the mainDocumentPart.ChartParts collection. But a ChartPart object does not seem to know where it's located in the document: chartPart.GetParentParts() only contains the mainDocumentPart.
I have multiple graphs in my document, so how can I distinguish them?
I have put my graphs in rich text controls, so I thought I could access them like that, but I cannot figure out how to do this. Retrieving the rich text control works, but how to find the graph within it?
foreach (SdtProperties sdtProp in mainDocumentPart.Document.Body.Descendants<SdtProperties>())
{
Tag tag = sdtProp.GetFirstChild<Tag>();
if (tag != null && tag.Val != null)
{
if (tag.Val == "containerX")
{
SdtProperties sdtPropTestResults = sdtProp;
// How to retrieve the graph part??
// sdtPropTestResults.Descendants<ChartPart> does not seem to work
}
}
}
Thanks a lot for your help.
Found a solution myself. I don't use the parent container now. Instead I gave the chart space an "Alt Title". Now my code looks for a drawing having a docProperties with the given title.
Here it is:
// Find our graphs by looping all drawings in the document and comparing their "alt title" property
foreach (Drawing drawing in mainDocumentPart.Document.Body.Descendants<Drawing>())
{
DocProperties docProperties = drawing.Descendants<DocumentFormat.OpenXml.Drawing.Wordprocessing.DocProperties>().FirstOrDefault();
if (docProperties != null && docProperties.Title != null)
{
if (docProperties.Title.Value == AltTitleChartBlack || docProperties.Title.Value == AltTitleChartRed)
{
LineChartData lineChartData = null;
switch (docProperties.Title.Value)
{
case AltTitleChartBlack:
lineChartData = this.chartDataBlack;
break;
case AltTitleChartRed:
lineChartData = this.chartDataRed;
break;
}
ChartReference chartRef = drawing.Descendants<ChartReference>().FirstOrDefault();
if (chartRef != null && chartRef.Id != null)
{
ChartPart chartPart = (ChartPart)mainDocumentPart.GetPartById(chartRef.Id);
if (chartPart != null)
{
Chart chart = chartPart.ChartSpace.Elements<Chart>().FirstOrDefault();
if (chart != null)
{
LineChart lineChart = chart.Descendants<LineChart>().FirstOrDefault();
if (lineChart != null)
{
LineChartEx chartEx = new LineChartEx(chartPart, lineChartData);
chartEx.Refresh();
chartPart.ChartSpace.Save();
}
}
}
}
}
}
}
Related
I've seen some questions address this problem domain and the unnecessary complexity of handling underlines (mainly applying them, but I want to detect them), but none that I can recall suggesting as I am here that the default strategies for accomplishing this create illogical false negatives. Furthermore, most of the previous questions I've referred to have used a different control (e.g. TextBlock) and/or have obselete syntax.
The problem
(.NET Core 3.1) I would simply like to programatically detect if a WPF RichTextBox selection contains any TextDecorations, but debugging shows that the TextDecorationCollection is always empty, even when the selection is all underlined.
As you can see, TextDecorationCollection returns empty even when examining a fully underlined Inline (Run)
For context, this screenshot just shows the plain text representation of the FlowDocument
What I've tried
1
TextRange myrange = new TextRange(MainRtb.Selection.Start, MainRtb.Selection.End);
if (myrange.GetPropertyValue(Inline.TextDecorationsProperty).Equals(TextDecorations.Underline)) { }
2
TextRange myrange = new TextRange(MainRtb.Selection.Start, MainRtb.Selection.End);
var obj = myrange.GetPropertyValue(Inline.TextDecorationsProperty);
if (obj == DependencyProperty.UnsetValue) {
log.addLog("mix format");
}
if (obj is TextDecorationCollection) {
var objProper = obj as TextDecorationCollection;
if (objProper.Count > 0) {
log.addLog("all underlined");
} else {
log.addLog("none underlined");
}
}
3
foreach (Block block in MainRtb.Document.Blocks) {
Paragraph p = block as Paragraph;
if (p != null) {
foreach (Inline inline in p.Inlines) {
InlineUIContainer iuic = inline as InlineUIContainer;
if (iuic != null) {
Console.WriteLine("found underline");
}
}
}
}
Theory
This post https://social.msdn.microsoft.com/Forums/vstudio/en-US/3ac626cf-60aa-427f-80e9-794f3775a70e/how-to-tell-if-richtextbox-selection-is-underlined?forum=wpf suggests that
myrange.GetPropertyValue(Inline.TextDecorationsProperty)
doesn't work properly due to an issue inside the "GetPropertyValue()" method, but it's a very old post. I couldn't run Jim's solution exactly because he initialises an "IEnumerable" which now needs to be declared with a type of some kind - at least that's what VS2019 said.
Test Rtf File:
https://docs.google.com/document/d/1YQmGsPcH4hX2XsP7KBdFqTFg4XjrSv8I/edit?usp=sharing&ouid=111968029811979231347&rtpof=true&sd=true
Try the following method:
public static void GetDecorations(RichTextBox rtb)
{
TextDecorationCollection decors = rtb.Selection.GetPropertyValue(Inline.TextDecorationsProperty) as TextDecorationCollection;
if (decors == null || decors.Count == 0)
{
if (rtb.Selection.Start.Parent is Run run)
{
if (run.Parent is Span span)
{
decors = span.TextDecorations;
}
else if (run.Parent is Paragraph para)
{
decors = para.TextDecorations;
}
}
}
if (decors is TextDecorationCollection tdc)
{
// TODO: Processing decorations...
}
}
I suppose the problem you are discovered is related to the particular structure of the FlowDocument after loading your RTF document and it might be described as follow.
When the RTF document is loaded for the underlined text tBox. See TextPointer for more information on text position terminology like "insertion position" a Run inline is created for this text, but the Run.TextDecorations property doesn't contain the actual decorations for this text. Instead of that the decorations settings are stored in the parent Span object that contains this Run. In another words, these decorations property is inherited from parent to child.
Therefore, if no decorations property is set on the current Run object, then you should to check the TextDecorations property in the parent object.
I'm using Load/Save layout similar way described on CodeProject. Catching LayoutSerializationCallback event and trying to find the corresponding viewModel for LayoutItem
private void LayoutSerializer_LayoutSerializationCallback(object sender, LayoutSerializationCallbackEventArgs e)
{
// This can happen if the previous session was loading a file
// but was unable to initialize the view ...
if (string.IsNullOrWhiteSpace(e.Model.ContentId) || (e.Content = ReloadItem(e.Model)) == null)
{
e.Cancel = true;
return;
}
}
private object ReloadItem(object item)
{
object ret = null;
switch (item)
{
case LayoutAnchorable anchorable:
//list of tools windows
ret = Manager.Tools.FirstOrDefault(i => i.ContentId == anchorable.ContentId);
if(ret == null && anchorable.ContentId.StartsWith(MapPanel.MapPanelPrefix))
{
MapPanels.Add(anchorable);
}
break;
case LayoutDocument document:
// list of restored documents
ret = Manager.Documents.FirstOrDefault(i => i.ContentId == document.ContentId);
break;
default:
throw new NotImplementedException("Not implemented type of AD item ");
}
return ret;
}
This works fine when I have all ViewModels available when deserializing/restoring layout.
But I'm thinking about something like delayed layout restore. In my case, I have some documents and some panels available at the start. But there can be some panels (call them MapPanel) that are loaded later (viewModels are loaded somewhere in future). And I can't figure out, how to restore layout for these panels.
For this case, I have List MapPanels to store anchorable that are loaded at avalondock layout load and trying to restore them in BeforeInsertAnchorable in ILayoutUpdateStrategy. But when I debug it, stored LayoutAnchorable has different parents that stored one. So I assume that after canceling (e.Cancel = true) in LayoutSerializationCallback somehow modifies not restored anchorable.
public bool BeforeInsertAnchorable(LayoutRoot layout, LayoutAnchorable anchorableToShow, ILayoutContainer destinationContainer)
{
if (anchorableToShow.Content is ToolPanel tool)
{
if(tool is MapPanel)
{
anchorableToShow = LayoutSaveLoadUtil.Instance.MapPanels.FirstOrDefault(mp => mp.ContentId == anchorableToShow.ContentId);
}
var destPane = destinationContainer as LayoutAnchorablePane;
if (destinationContainer != null && destinationContainer.FindParent<LayoutFloatingWindow>() != null)
return false;
var dockLeftPane = layout.Descendents().OfType<LayoutAnchorablePane>().FirstOrDefault(d => d.Name == tool.PreferredLocation + "Pane");
if (dockLeftPane != null)
{
dockLeftPane.Children.Add(anchorableToShow);
return true;
}
return false;
}
return false;
}
So I'm curious what is the right approach to achieve this. I was also thinking about restoring layout (again) after MapPanel is loaded, but I don't know how to skip all other LayoutItems. So is there any possibility of how to restore a single Anchorable position, floating parent, docking, size, etc...?
So i probably figured out solution.
I have some panels (call them MapPanel) and theyr content isnt loaded when layout is restored from XML. In my case i have app, that has some documents and tabs, and in aditional, user can load aditional data to show maps.
And i needed to restore layout of this maps when user load them. (click button, selec where to load maps etc)
I have static class called LayoutSaveLoadUtil (as described on codeproject) when i have aditional list MapPanelsStorage of type LayoutAnchorable. To this list i store all layouts that missing content on layout restore and ContentId has specific prefix. This tells me that is MapPanel. Then i create a dummy content, assign it to this panel and set its visibility to false (so panel is invisible)
(this is called in layoutSerializationCallback)
private object ReloadItem(object item)
{
object ret = null;
switch (item)
{
case LayoutAnchorable anchorable:
//list of tools windows
ret = Manager.Tools.FirstOrDefault(i => i.ContentId == anchorable.ContentId);
if(ret == null && anchorable.ContentId.StartsWith(MapPanel.MapPanelPrefix))
{
//when layoutAnchorable is MapPanel (has MapPanel prefix) and its content is loaded yet
//store its layout into list to restore it later (when content is loaded)
MapPanelsStorage.Add(anchorable);
//Set anchorable visibility to false
anchorable.IsVisible = false;
//return dummy model for avalondock layout serialization
ret = new EmptyMapViewModel();
}
break;
case LayoutDocument document:
// list of restored documents
ret = Manager.Documents.FirstOrDefault(i => i.ContentId == document.ContentId);
break;
default:
throw new NotImplementedException("Not implemented type of AD item ");
}
return ret;
}
Then in BeforeInsertAnchorable (called when new panel is added into layout) i check if panel content is MapPanel, look for stored layout, try to find parent (LayoutAnchorablePane/LayoutDocumentPane) and then add it instead of DummyHidden panel, and just remove it from storage.
//hacky hacky to restore map panel layout when map is opened after layout is loaded
//in layout deserialization, deserialize layout for MapPanels that hasnt DataModels yet with DummyModel to preserve their layout
if (anchorableToShow.Content is MapPanel mappanel)
{
var storedMapsLayout = LayoutSaveLoadUtil.Instance.MapPanelsStorage;
//check if Map panel has stored layout from previous layout deserialization
var matchingAnchorable = storedMapsLayout.FirstOrDefault(m => m.ContentId == mappanel.ContentId);
if (matchingAnchorable != null)
{
//make preserved layout visible, so its parent and etc is restored. Without this, correct parent LayoutGroup isnt found
matchingAnchorable.IsVisible = true;
LayoutAnchorablePane matchingAnchorablePane;
LayoutDocumentPane matchingDocumentPane;
//find parent layoutGroup. This can be LayoutAnchorablePane or LayoutDocumentPane
if ((matchingAnchorablePane = matchingAnchorable.FindParent<LayoutAnchorablePane>()) != null)
{
//add new panel into layoutGroup with correct layout
matchingAnchorablePane.Children.Add(anchorableToShow);
//remove old dummy panel
matchingAnchorablePane.RemoveChild(matchingAnchorable);
//remove restored layout from storage
storedMapsLayout.Remove(matchingAnchorable);
return true;
}
else if ((matchingDocumentPane = matchingAnchorable.FindParent<LayoutDocumentPane>()) != null)
{
//add new panel into layoutGroup with correct layout
matchingDocumentPane.Children.Add(anchorableToShow);
//remove old dummy panel
matchingDocumentPane.RemoveChild(matchingAnchorable);
//remove restored layout from storage
storedMapsLayout.Remove(matchingAnchorable);
return true;
}
else
{
matchingAnchorable.IsVisible = false;
}
}
}
I am trying to white an application that gets a url and find out all html5 canvas advertisement (i found out that iframes is the best HTMLElement that I can get) and take a video screenshot of the element.
I have to render the page (because it uses javascript calls to create the final page) using an in memory webbrowser control and then I find all iframes from document of the Webbrowser control (objDoc in the sample).
var aColl = from HtmlElement p in objDoc.Body.All
where Configuration.lstCanvas.Contains(p.TagName.ToUpper())
select p;
For each element I have to capture a screenshot and a video of the content of the iframe or even an html file that can represent the video later.
In order to capture the image I use the following code (if i pass null to HTMLelement I get a full screen capture) as an extended metod of the webbrowser control.
public static Image GetElementImage(this WebBrowser web, HtmlElement elm = null)
{
try
{
IHTMLDocument2 idoc2 = web.Document.DomDocument as IHTMLDocument2;
if (idoc2 == null)
{
return null;
}
IHTMLElementCollection elementCollection = idoc2.all;
if (elementCollection == null)
{
return null;
}
IHTMLElementRender iHTMLElementRender;
if (elm != null)
{
iHTMLElementRender = (IHTMLElementRender)elm.DomElement;
if (iHTMLElementRender == null)
{
return null;
}
}
else {
iHTMLElementRender = idoc2.body as IHTMLElementRender;
if (iHTMLElementRender == null)
{
return null;
}
}
// get the location and size of the element and create a bitmap
// need a different interface
IHTMLElement element = iHTMLElementRender as IHTMLElement;
if (element == null)
{
return null;
}
int elementWidth = element.offsetWidth;
int elementHeight = element.offsetHeight;
// Create a bitmap and render the element to it.
Bitmap memoryBitmap = new Bitmap(elementWidth, elementHeight, PixelFormat.Format24bppRgb);
Graphics memGraphics = Graphics.FromImage(memoryBitmap);
IntPtr memDC = memGraphics.GetHdc();
iHTMLElementRender.DrawToDC(memDC);
memGraphics.ReleaseHdc(memDC);
Image r = (Image)memoryBitmap.Clone();
memGraphics.Dispose();
memoryBitmap.Dispose();
return r;
}
catch // (Exception ex)
{
return null;
}
}
I haven't found a way to take a video capture of the element.
I try to grab the content of the iframe as html (in order to save it and use it for latter) but I come out with some issues:
The url is not always an attribute of the iframe (it changes from the javascript)
Cross Domain scripting is not letting me to gram the html
The file that I download is not allways reproduces the content of the iframe
Do you have any suggestion either on getting the html code or the video?
Thanks in advance
I need to access a grid in a list-view data-template, but when using this code the program reaches the foreach loop and don't execute it
foreach (Grid firstgrid in Active_list.Items)
{
var item = Active_list.ItemContainerGenerator.ContainerFromItem(firstgrid);
var ch = AllChildren(item);
var tag = url;
var control = (Grid)ch.First(c => c.Tag == tag);
if (firstgrid.GetType() == typeof(Grid))
{
if ((String)firstgrid.Tag == url)
{
foreach (ProgressBar prg in firstgrid.Children)
{
if (prg.GetType() == typeof(ProgressBar))
{
prg.IsIndeterminate = false;
}
}
foreach (TextBlock txt in firstgrid.Children)
{
if (txt.GetType() == typeof(TextBlock))
{
txt.Visibility = Visibility.Visible;
}
}
}
}
}
This code Active_list.Items won't give you any control but your actual data. If you want to access specific control in you list view, you need to go through your visual tree and find it manually. I think it's not a good practice to manually change controls inside list view...
But if you really want to do it this way I recommend you to check out this topic with similar question: How to access a specific item in a Listbox with DataTemplate?
I made MVVM application using DevExpress and WPF. To show data NamesView.xaml View will be called in Document Tab Provided by DevExpress. Code is:
void ShowDocument(Int32? key)
{
if(DocumentManagerService == null)
return;
IDocument document = (key != null && key.HasValue) ?
GetDocumentById(key.Value) : null;
if(document == null)
{
document = DocumentManagerService.CreateDocument("NamesView", key, this);
}
document.Show();
}
There is a PanelB in NamesCollectionView.xaml file.How to Show "NamesView.xaml" in Panel(named as PanelB defined in NamesCollectionView.xaml file rather than showing in Document Tab?. When user will Click on Edit or new button, NamesView file will get open in response.I want that this file to show in PanelB.
Note: I f my Question is not clear or if you need some more code to figure it out. Please ask me, i will provide you more detail.