This project has moved. For the latest updates, please go here.

Table.AutoFit issue

Apr 5, 2012 at 8:23 PM
Edited Apr 8, 2012 at 11:56 AM

Hi,

I have an issue with Table.AutoFit. If I set AutoFit to ColumnWidth it still does not work the way it does if set in Word (2010). When choosing Fixed Column Width in Word, "Table Properties / Table Options / Automatically resize to fit contents" is unchecked (column widths are fixed and content is wrapped). In the Open XML the following happens:

...
<w:tbl>
   <w:tblPr>
      <w:tblStyle w:val="TableGrid"/>
      <w:tblW w:w="0" w:type="auto"/>
      <w:tblLayout w:type="fixed"/>
      ...

i.e. tblW element type attribute is set to "auto" rather than "dxa" (all column properties tcW element type attributes are set to "dxa" however), and there is another element, tblLayout, with attribute type set to "fixed" (which reflects the "Automatically resize to fit contents" option).

I got it to work the way I wanted by altering the AutoFit property code to reflect this, thus now I am able to set column widths manually when AutoFit is set to ColumnWidth, and content does not resize the columns.

Roger

<w:tblWi.e. tblW type is set to "auto" rather than "xda", and there is another parameter tblLayout with type set to "fixed" (which reflects the "Automatically resize to fit contents" option). w:w="0"

w:type="auto"/>

I.e. tblW type is "auto" rather than "xda", and there is another parameter tblLayout with type set to "fixed" which reflects the "Automatically

Apr 5, 2012 at 8:42 PM
Edited Apr 9, 2012 at 9:53 PM

The modified AutoFit code I used (probably not the best way of implementing it though):

/// <summary>
/// Auto size this table according to some rule.
/// </summary>
public AutoFit AutoFit
{
   get { return autofit; }

   set
   {
      string tableAttributeValue = string.Empty;
      string columnAttributeValue = string.Empty;
      switch (value)
      {
         case AutoFit.ColumnWidth:
         {
            tableAttributeValue = "auto";
            columnAttributeValue = "dxa";

            // Disable "Automatically resize to fit contents" option
            XElement tblPr = Xml.Element(XName.Get("tblPr", DocX.w.NamespaceName));
            XElement layout = tblPr.Element(XName.Get("tblLayout", DocX.w.NamespaceName));
            if (layout == null)
            {
               tblPr.Add(new XElement(XName.Get("tblLayout", DocX.w.NamespaceName)));
               layout = tblPr.Element(XName.Get("tblLayout", DocX.w.NamespaceName));
            }

            XAttribute type = layout.Attribute(XName.Get("type", DocX.w.NamespaceName));
            if (type == null)
            {
               layout.Add(new XAttribute(XName.Get("type", DocX.w.NamespaceName), ""));
               type = layout.Attribute(XName.Get("type", DocX.w.NamespaceName));
               type.Value = "fixed";
            }

            break;
         }

         case AutoFit.Contents:
         {
            tableAttributeValue = columnAttributeValue = "auto";
            break;
         }

         case AutoFit.Window:
         {
            tableAttributeValue = columnAttributeValue = "pct";
            break;
         }
      }

      // Set table attributes
      var query = from d in Xml.Descendants()
            let type = d.Attribute(XName.Get("type", DocX.w.NamespaceName))
            where (d.Name.LocalName == "tblW") && type != null
            select type;

      foreach (XAttribute type in query)
         type.Value = tableAttributeValue;

      // Set column attributes
      query = from d in Xml.Descendants()
            let type = d.Attribute(XName.Get("type", DocX.w.NamespaceName))
            where (d.Name.LocalName == "tcW") && type != null
            select type;

      foreach (XAttribute type in query)
         type.Value = columnAttributeValue;

      autofit = value;
   }
}

Roger

Developer
Apr 5, 2012 at 9:04 PM

Great Roger, finally someone figured it out :-) DocX is getting more and more popular! Maybe you could send it via message or so to Cathal to add it to code base. Or maybe you would be able to work on the DocX project a bit too :-)

Developer
Apr 5, 2012 at 9:06 PM

The only problem is it seems you are using linq, net 4.0 and the code base uses earlier framework

Apr 5, 2012 at 10:21 PM

The code base is already using linq and targets net 3.5. I did not change any of this, actually I did not do much, as you can see: 

Original AutoFit code:

/// <summary>
/// Auto size this table according to some rule.
/// </summary>
public AutoFit AutoFit
{
   get { return autofit; }

   set
   {
      string attributeValue = string.Empty;
      switch (value)
      {
         case AutoFit.ColumnWidth:
         {
            attributeValue = "dxa";
            break;
         }

         case AutoFit.Contents:
         {
            attributeValue = "auto";
            break;
         }

         case AutoFit.Window:
         {
            attributeValue = "pct";
            break;
         }
      }

      var query = from d in Xml.Descendants()
               let type = d.Attribute(XName.Get("type", DocX.w.NamespaceName))
               where (d.Name.LocalName == "tcW" || d.Name.LocalName == "tblW") && type != null
               select type;

      foreach (XAttribute type in query)
         type.Value = attributeValue;

      autofit = value;
   }
}

Roger

Developer
Apr 5, 2012 at 10:25 PM

Ah great then ;-)

Apr 5, 2012 at 10:29 PM

With that said, I believe Cathal will pick up on this and implement it in next release. His implementation will be better than mine.

Roger

Apr 5, 2012 at 11:55 PM
Edited Apr 9, 2012 at 9:53 PM

I guess it should be:

 

/// <summary>
/// Auto size this table according to some rule.
/// </summary>
public AutoFit AutoFit
{
   get { return autofit; }

   set
   {
      string tableAttributeValue = string.Empty;
      string columnAttributeValue = string.Empty;
      switch (value)
      {
         case AutoFit.ColumnWidth:
         {
            tableAttributeValue = "auto";
            columnAttributeValue = "dxa";

            // Disable "Automatically resize to fit contents" option
            XElement tblPr = Xml.Element(XName.Get("tblPr", DocX.w.NamespaceName));
            XElement layout = tblPr.Element(XName.Get("tblLayout", DocX.w.NamespaceName));
            if (layout == null)
            {
               tblPr.Add(new XElement(XName.Get("tblLayout", DocX.w.NamespaceName)));
               layout = tblPr.Element(XName.Get("tblLayout", DocX.w.NamespaceName));
            }

            XAttribute type = layout.Attribute(XName.Get("type", DocX.w.NamespaceName));
            if (type == null)
            {
               layout.Add(new XAttribute(XName.Get("type", DocX.w.NamespaceName), ""));
               type = layout.Attribute(XName.Get("type", DocX.w.NamespaceName)); 
            }

            type.Value = "fixed";
            break;
         }

         case AutoFit.Contents:
         {
            tableAttributeValue = columnAttributeValue = "auto";
            break;
         }

         case AutoFit.Window:
         {
            tableAttributeValue = columnAttributeValue = "pct";
            break;
         }
      }

      // Set table attributes
      var query = from d in Xml.Descendants()
            let type = d.Attribute(XName.Get("type", DocX.w.NamespaceName))
            where (d.Name.LocalName == "tblW") && type != null
            select type;

      foreach (XAttribute type in query)
         type.Value = tableAttributeValue;

      // Set column attributes
      query = from d in Xml.Descendants()
            let type = d.Attribute(XName.Get("type", DocX.w.NamespaceName))
            where (d.Name.LocalName == "tcW") && type != null
            select type;

      foreach (XAttribute type in query)
         type.Value = columnAttributeValue;

      autofit = value;
   }
}

 

Roger

Coordinator
Apr 9, 2012 at 2:06 PM

Roger,

Thank you for your contribution to DocX. I have just added this as change-set 75372

Kind regards and happy coding,
Cathal

P.S. Thanks for the reminder MadBoy, what would DocX or I do without your help.

Apr 9, 2012 at 9:52 PM
Edited Apr 9, 2012 at 10:46 PM

Catchal,

I came to think that when changing AutoFit from ColumnWidth to Contents or Window, the tblLayout element needs to be removed (and it seems w attribute of tblW element needs to be set to 5000 when changing to AutoFit.Window). Perhaps like this:

case AutoFit.Contents:
 {
	tableAttributeValue = columnAttributeValue = "auto";

	// Enable "Automatically resize to fit contents" option
	XElement tblPr = Xml.Element(XName.Get("tblPr", DocX.w.NamespaceName));
	if (tblPr != null)
	{
		XElement tblLayout = tblPr.Element(XName.Get("tblLayout", DocX.w.NamespaceName));
		if (tblLayout != null)
			tblLayout.Remove();
	}

	break;
 }

case AutoFit.Window:
 {
	tableAttributeValue = columnAttributeValue = "pct";

	// Enable "Automatically resize to fit contents" option
	XElement tblPr = Xml.Element(XName.Get("tblPr", DocX.w.NamespaceName));
	if (tblPr != null)
	{
		XElement tblLayout = tblPr.Element(XName.Get("tblLayout", DocX.w.NamespaceName));
		if (tblLayout != null)
			tblLayout.Remove();

		XElement tblW = tblPr.Element(XName.Get("tblW", DocX.w.NamespaceName));
		// Need to check if tblW is null?
		XAttribute width = tblW.Attribute(XName.Get("w", DocX.w.NamespaceName));
		if (width == null)
		{
			tblW.Add(new XAttribute(XName.Get("w", DocX.w.NamespaceName), String.Empty));
			width = tblW.Attribute(XName.Get("w", DocX.w.NamespaceName));
		}

		width.Value = "5000";
	}

	break;
 }

 

 

I see you fixed my code to also check if tblPr is null:

// Disable "Automatically resize to fit contents" option
XElement tblPr = Xml.Element(XName.Get("tblPr", DocX.w.NamespaceName));
if (tblPr != null)
{
	XElement layout = tblPr.Element(XName.Get("tblLayout", DocX.w.NamespaceName));
	if (layout == null)
	{
		tblPr.Add(new XElement(XName.Get("tblLayout", DocX.w.NamespaceName)));
		layout = tblPr.Element(XName.Get("tblLayout", DocX.w.NamespaceName));
	}

	XAttribute type = layout.Attribute(XName.Get("type", DocX.w.NamespaceName));
	if (type == null)
	{
		layout.Add(new XAttribute(XName.Get("type", DocX.w.NamespaceName), String.Empty));
		type = layout.Attribute(XName.Get("type", DocX.w.NamespaceName));
	}

	type.Value = "fixed";
}

 

What I did was to copy and alter code from Design property, thus probably a good idea to also check for null here:

XElement tblPr = Xml.Element(XName.Get("tblPr", DocX.w.NamespaceName));
XElement style = tblPr.Element(XName.Get("tblStyle", DocX.w.NamespaceName));
if (style == null)
{
	tblPr.Add(new XElement(XName.Get("tblStyle", DocX.w.NamespaceName)));
	style = tblPr.Element(XName.Get("tblStyle", DocX.w.NamespaceName));
}

XAttribute val = style.Attribute(XName.Get("val", DocX.w.NamespaceName));
if (val == null)
{
	style.Add(new XAttribute(XName.Get("val", DocX.w.NamespaceName), ""));
	val = style.Attribute(XName.Get("val", DocX.w.NamespaceName));
}

design = value;

 

Keep up the good work!

Roger

Apr 9, 2012 at 11:22 PM
Edited Apr 10, 2012 at 2:49 PM

Actually what seems to happen, as far as I can tell, when AutoFit is changed in Word:

AutoFit.Contents

tblW element:

 <w:tblW w:w="0" w:type="auto"/>

 

tcW elements:

<w:tcW w:w="0" w:type="auto"/>

 

AutoFit.Window

tblW element:

<w:tblW w:w="5000" w:type="pct"/>

 

tcW elements:

<w:tcW w:w="X" w:type="pct"/>

...where X is relative size of the different columns prior to changing to AutoFit.Window.

 

AutoFit.ColumnWidth

tblW element:

<w:tblW w:w="0" w:type="auto"/>

...however if column widths are set manually by user:

<w:tblW w:w="X" w:type="dxa"/>

...where X is width of entire table

 

tblLayout element:

<w:tblLayout w:type="fixed"/>

 

tcW elements:

<w:tcW w:w="X" w:type="dxa"/>

...where X is set to width of column.

 

Thus for AutoFit.Window width for the tcW elements needs to be calculated. This is simple when changing from AutoFit.ColumnWidth, but when changing from AutoFit.Contents the widths are 0, thus the relative widths need to be calculated based on the content.

Roger