views:

3606

answers:

5

I want to set the width of a TextBlock based on the width of its container, minus the margins set on the TextBlock.

Here is my code

<TextBlock x:Name="txtStatusMessages" 
           Width="{Binding ElementName=LayoutRoot,Path=ActualWidth }"
                   TextWrapping="WrapWithOverflow" 
           Foreground="White" 
           Margin="5,5,5,5">This is a message
</TextBlock>

And that works great except for the fact that the TextBlock is 10 units too big due to the Left and Right Margins bbeing set to 5.

OK, so I thought... Let's use a Converter. But I don't know how to pass the ActualWidth of my container control (SEE ABOVE: LayoutRoot).

I know how to use converters, and even converters with parameters, just not a parameter like... Binding ElementName=LayoutRoot,Path=ActualWidth

For example, I can't make this work...

Width="{Binding Converter={StaticResource PositionConverter},  
       ConverterParameter={Binding ElementName=LayoutRoot,Path=ActualWidth }}"

I hope I made this clear enough and hope that you can help because Google is no help for me tonight.

TIA!

Doug

+1  A: 

Hi,

you're supposed to use the other control as the source, not the parameter. The parameter has to be a constant and in your case can be -5.

I'm not near VS at the moment so the syntax maybe inaccurate, however, it is something like:

Width="{Binding ElementName=LayoutRoot, Path=ActualWidth,
Converter={StaticResource PositionConverter}, ConverterParameter=-5}"

(The converter will receive -5 as a string and will have to convert it into a number before using it.)

From my experience it is better to use the OnXXXChanged callback of DependecyProperty XXX, and not bind controls within the same window/root control one to another. One of the reasons for this is that you may want to bind them to an external element later on.

Danny Varod
Thanks Danny. This worked just fine. But I was hoping to not have to hard code the Parameter value. I didn't know that the Parameter needed to be a constant. Thanks!
Doug
+1  A: 

Although I suspect there may be a better way to solve your problem, I think I have an answer for what you want to do. ( You didn't mention what type your container is. A StackPanel for instance takes care of the width calculation for you. See TextBox#2 below)

First the XAML

<Window x:Class="WpfApplication1.Window2" ...
    xmlns:local="clr-namespace:WpfApplication1"
    Title="Window2" Height="300" Width="300">
    <Window.Resources>
        <local:WidthSansMarginConverter x:Key="widthConverter" />
    </Window.Resources>
    <Grid>
        <StackPanel x:Name="stack">
            <TextBlock x:Name="txtStatusMessages" 
                    Width="{Binding ElementName=stack,Path=ActualWidth, 
                        Converter={StaticResource widthConverter}}"
                    TextWrapping="WrapWithOverflow" 
                    Background="Aquamarine" 
                    Margin="5,5,5,5">
                This is a message
            </TextBlock>
            <TextBlock x:Name="txtWhatsWrongWithThis" 
                    TextWrapping="WrapWithOverflow" 
                    Background="Aquamarine" 
                    Margin="5,5,5,5">
                This is another message
            </TextBlock>
        </StackPanel>
    </Grid>
</Window>

Next the Converter. We have a problem here.. since the ConverterParameter for the Convert methods cannot be a dynamic value for some reason. So we sneak in the Textbox Margin via a public property of the Converter that we set in Window's ctor. WidthSansMarginConverter.cs

public class WidthSansMarginConverter : IValueConverter
    {
        private Thickness m_Margin = new Thickness(0.0);

        public Thickness Margin
        {
            get { return m_Margin; }
            set { m_Margin = value; }
        }
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (targetType != typeof(double)) { return null; }

            double dParentWidth = Double.Parse(value.ToString());
            double dAdjustedWidth = dParentWidth-m_Margin.Left-m_Margin.Right;
            return (dAdjustedWidth < 0 ? 0 : dAdjustedWidth);
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }

Window2.xaml.cs

        public Window2()
        {
            InitializeComponent();

            WidthSansMarginConverter obConverter = this.FindResource("widthConverter") as WidthSansMarginConverter;
            obConverter.Margin = txtStatusMessages.Margin;
        }

HTH. Thanks for the exercise :)

Gishu
Thnaks Gishu! Your sample helped me learn more about converters. Altough your answer worked, I ended marking danny's answer as THE answer because it was the solution that I used. I was tired and lazy and his, altough less flexible, was less code. Thanks!
Doug
A: 

If your textbox is a direct child of LayoutRoot, just set the the following property in your textbox

HorizontalAlignment="Stretch"
viggity
A: 

Hi, new answer:

Use multibinding:


<TextBlock>
    <TextBlock.Width>
        <MultiBinding Converter="{StaticResource yourConverter}">
          <MultiBinding.Bindings>
            <Binding /> <!-- Bind to parameter 1 here -->
            <Binding /> <!-- Bind to parameter 2 here -->
          </MultiBinding.Bindings>
        </MultiBinding>
    </TextBlock.Width>
</TextBlock>

and and a converter which converts the two parameters to the value you want.

Danny Varod
+1  A: 

yes..multi binding works for me.. actually i tried to send a element as a convereterparameter, but its not accepting. thats why i passed the element as a value to the converter class.

below is my example..

                    <GridViewColumn.Width>
                             <MultiBinding Converter="{StaticResource GetWidthfromParentControl}">
                            <MultiBinding.Bindings>
                                <Binding ElementName="lstNetwork" Path="ActualWidth"/>
                                <Binding ElementName="MyGridView"/>
                            </MultiBinding.Bindings>
                        </MultiBinding>
                    </GridViewColumn.Width>

.... .... .... ...

In window resize, my first gridviewcolumn has to be resized, not the other two gridviewcolumns.. i passed Actualwidth of listview and also total gridview object as an element.. if you go the converter code...

class GetWidthfromParentControl : IMultiValueConverter { #region IMultiValueConverter Members

    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        GridView view = values[1] as GridView;
        GridViewColumnCollection collc = view.Columns;
        double actualWidths = collc[1].ActualWidth + collc[2].ActualWidth;
        return ((double)values[0] - actualWidths );
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        return null;
    }

    #endregion
}

this worked for me... :)