Search code examples
c#wpfxamlcastingmultibinding

How to Bind Sum of width of two control to MinWidth of another control


I wanna set MinWidth of column of grid, which should be sum of Actual Width of a Label and a Button control inside another grid placed inside the cell of a column. I am using a Converter class for this purpose but figure out the XAML part. Converter is here:

class StringSumtoIntConverter : IMultiValueConverter
{
    public object Convert(object[] value, Type targetType, object parameter, CultureInfo culture)
    {
        int sum = 0;
        foreach (var item in value)
        {
            sum += System.Convert.ToInt32(item);
        }

        return sum;
    }
    //...Other implementations
}

XAML code I have written till now is:

xmlns:helperClasses="clr-namespace:EmbroidaryManagementSystem_V2._0.HelperClasses" <!--Import class-->

<helperClasses:StringSumtoIntConverter x:Key="StringSumtoIntConvert"/>  <!--Inside Window.Resources tag-->

<ColumnDefinition Width="48*"> <!--Inside Grid-->
    <ColumnDefinition.MinWidth>
        <MultiBinding Converter="{StaticResource StringSumtoIntConvert}">
            <Binding Path=""></Binding>
        </MultiBinding>
    </ColumnDefinition.MinWidth>
</ColumnDefinition>

Complete implementation of XAML is here:

<Window x:Class="EmbroidaryManagementSystem_V2._0.View.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:EmbroidaryManagementSystem_V2._0.View"
    xmlns:y="clr-namespace:EmbroidaryManagementSystem_V2._0.ViewModel.CollectionsViewModel"
    xmlns:helperClasses="clr-namespace:EmbroidaryManagementSystem_V2._0.HelperClasses"
    mc:Ignorable="d"
    Title="MainWindow" Height="655.512" Width="1120.159" FontSize="24" WindowStartupLocation="CenterScreen"
    >
<Window.Resources>
    <helperClasses:StringSumtoIntConverter x:Key="StringSumtoIntConvert"/>
</Window.Resources>
<Window.DataContext>
    <y:ClientCollectionVM/>
</Window.DataContext>
<Grid HorizontalAlignment="Left" Height="622" VerticalAlignment="Top" Width="1110" Background="#FFD6DBE9">
    <Grid.RowDefinitions>
        <RowDefinition Height="89*"/>
        <RowDefinition Height="39*"/>
        <RowDefinition Height="494*"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="137*"/>
        <ColumnDefinition Width="48*">
            <ColumnDefinition.MinWidth>
                <MultiBinding Converter="{StaticResource StringSumtoIntConvert}">
                    <Binding Path=""></Binding>
                </MultiBinding>
            </ColumnDefinition.MinWidth>
        </ColumnDefinition>
    </Grid.ColumnDefinitions>
    <GridSplitter x:Name="gridSplitter" Grid.Column="1" HorizontalAlignment="Left" Height="533" Grid.Row="1" 
                  VerticalAlignment="Top" Width="2" Grid.RowSpan="2"/>
    <Grid Grid.Column="1" Height="35" Background="#FF657695" 
          Grid.Row="1" VerticalAlignment="Top">
        <Label x:Name="lblNotificationsHeader" Content="Notifications" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="14.667" Height="30" Foreground="#FFEBF0EE"/>
        <Button x:Name="btnNotificationsClose" Content="X" 
                Margin="0,5,8,0" VerticalAlignment="Top" Width="20" FontFamily="Verdana" HorizontalAlignment="Right" Background="Transparent" FontSize="13.333" Foreground="Black"/>
    </Grid>
</Grid>

Also there is an error saying: Unable to cast object of type 'EmbroidaryManagementSystem_V2._0.ViewModel.CollectionsViewModel.ClientCollectionVM' to type 'System.IConvertible'. At line:

<MultiBinding Converter="{StaticResource StringSumtoIntConvert}">
                        <Binding Path=""></Binding>
                    </MultiBinding>

I don't know why.


Solution

  • You need to provide the values to the converter in the MultiBinding.

    <MultiBinding Converter="{StaticResource StringSumtoIntConvert}">
        <Binding ElementName="lblNotificationsHeader" Path="ActualWidth" />
        <Binding ElementName="btnNotificationsClose" Path="ActualWidth" />
    </MultiBinding>
    

    The error is because Path is empty, which means it's picking up the DataContext and attempting to pass it to the converter which is unable to cast ClientCollectionVM to an int.

    I'm not 100% sure where you are going with all of this, but you could probably nix the converter and just do:

    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="Auto" />
    </Grid.ColumnDefinitions>
    

    The column would then expand to fit it's contents. Of course then you would want to change the inner Grid to a StackPanel with Horizontal orientation:

    <StackPanel Grid.Row="1"
                    Grid.Column="1"
                    Height="35"
                    VerticalAlignment="Top"
                    Background="#FF657695"
                    Orientation="Horizontal">
            <Label x:Name="lblNotificationsHeader"
                   Height="30"
                   HorizontalAlignment="Left"
                   VerticalAlignment="Top"
                   Content="Notifications"
                   FontSize="14.667"
                   Foreground="#FFEBF0EE" />
            <Button x:Name="btnNotificationsClose"
                    Margin="0,5,8,0"
                    HorizontalAlignment="Right"
                    VerticalAlignment="Top"
                    Background="Transparent"
                    Content="X"
                    FontFamily="Verdana"
                    FontSize="13.333"
                    Foreground="Black" />
        </StackPanel>
    

    Either that or give the inner Grid it's own column definitions to separate the Label and Button out.

    Update

    Based on your update to add the GridSplitter, I've included a full example of how to achieve what you are after.

    Keep in mind that, in your example, the GridSplitter is really resizing the left column no matter which direction you drag it, and the right column is just expanding to fill. So our goal is not to have a MinWidth on your right column, but instead a MaxWidth on our left column.

    One quick change to your converter, since ActualWidth and ActualHeight are doubles, let's change the converter to use doubles instead of integers so that we do not end up with stray pixels.

    public object Convert(object[] value, Type targetType, object parameter, CultureInfo culture)
        {
            double sum = 0;
            foreach (var item in value)
            {
                sum += System.Convert.ToDouble(item);
            }
    
            return sum;
        }
    

    We're going to put the GridSplitter in it's own column, set that columns width to "2" set the first ColumnDefinition's width to "4*" and the third to "*". The "4*" is really just a random number, star sizing works off of percentages, so 4 won't always work, it just did in this case.

    We also need to add some columns to the inner grid, we'll give those a name and use those elements to get the widths for our MultiBinding. We do that instead of getting the widths from the elements themselves, because ActualWidth does not include margins.

    <Grid Background="#FFD6DBE9">
        <Grid.RowDefinitions>
            <RowDefinition Height="89*" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="494*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="4*" />
            <ColumnDefinition Width="2" />
            <ColumnDefinition Width="*">
                <ColumnDefinition.MinWidth>
                    <MultiBinding Converter="{StaticResource StringSumtoIntConvert}">
                        <Binding ElementName="cdNotificationHeaderLabel" Path="ActualWidth" />
                        <Binding ElementName="cdNotificationHeaderButton" Path="ActualWidth" />
                    </MultiBinding>
                </ColumnDefinition.MinWidth>
            </ColumnDefinition>
        </Grid.ColumnDefinitions>
        <GridSplitter x:Name="gridSplitter"
                      Grid.Row="1"
                      Grid.RowSpan="2"
                      Grid.Column="1"
                      Width="2"
                      ResizeBehavior="PreviousAndNext" />
        <Grid Grid.Row="1"
              Grid.Column="2"
              Height="35"
              Background="#FF657695">
            <Grid.ColumnDefinitions>
                <ColumnDefinition x:Name="cdNotificationHeaderLabel" Width="Auto" />
                <ColumnDefinition Width="*" />
                <ColumnDefinition x:Name="cdNotificationHeaderButton" Width="Auto" />
            </Grid.ColumnDefinitions>
            <Label x:Name="lblNotificationsHeader"
                   Margin="0 0 5 0"
                   HorizontalAlignment="Left"
                   VerticalAlignment="Center"
                   Content="Notifications"
                   FontSize="14.667"
                   Foreground="#FFEBF0EE" />
            <Button x:Name="btnNotificationsClose"
                    Grid.Column="2"
                    HorizontalAlignment="Right"
                    VerticalAlignment="Center"
                    Background="Transparent"
                    Content="X"
                    FontFamily="Verdana"
                    FontSize="13.333"
                    Foreground="Black" />
        </Grid>
    </Grid>
    

    All that said, I'd recommend deriving a custom control from GridSplitter and handling it that way, but the above should help to get you up and running.