wpfmvvmivalueconverter

WPF Binding.Converter in i:InvokeCommandAction.CommandParameter can only be called once


When the Grid component size changes, I want to get its ActualWidth and ActualHeight properties so that the contents of the canvas in the Grid could be recalculated to fit. So, taking this post(https://stackoverflow.com/a/72573639/9949419) as a reference, I add an event trigger to the Grid and pass out its ActualWidth and ActualHeight properties trough a Converter. The code is as below.

Xaml code:

<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<!-- This is the Grid which I want to get its ActualWidth and ActualHeight properties.-->
<Grid x:Name="GraphicGrid">
    <!-- K value-->
    <ItemsControl ItemsSource="{Binding RectItems}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemContainerStyle>
            <Style TargetType="ContentPresenter">
                <Setter Property="Canvas.Left" Value="{Binding X}"/>
                <Setter Property="Canvas.Bottom" Value="{Binding Y}"/>
            </Style>
        </ItemsControl.ItemContainerStyle>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Rectangle Width="{Binding Width}" Height="{Binding Height}" Fill="{Binding _Brush}"/>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>      
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SizeChanged" >
            <i:InvokeCommandAction Command="{Binding OnSizeChanged}">
                <i:InvokeCommandAction.CommandParameter>
                    <Binding ElementName="GraphicGrid">
                        <Binding.Converter>
                            <!-- This this the value converter that passes the ActualWidth and ActualHeight properties to the event method.-->
                            <local:SizeConverter/>
                        </Binding.Converter>
                    </Binding>
                </i:InvokeCommandAction.CommandParameter>
            </i:InvokeCommandAction>
        </i:EventTrigger>
    </i:Interaction.Triggers>        
</Grid>                        
View model code:
Public ICommand OnSizeChanged{get;set;}
Public MainWindowViewModel()
{
    OnSizeChanged = new RelayCommand<double,double)>(Resize);
}
public void Resize((double,double)size)
{
    // TODO:
}

SizeConverter code:

 public class SizeConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value is FrameworkElement fe ?(fe.ActualWidth, fe.ActualHeight):(double.NaN,double.NaN);
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

When the project starts, SizeConverter() is called and get a (0,0) as a result(I assume this is the default value of the Grid before all elements have been loaded). However after start up initialization, no matter how I change the window size, only the Resize() method is called. The SizeConverter() is never called again, so the event parameter still remains (0,0). Please someone tell me how to fix this problem. Thanks.


Solution

  • There is nothing in MVVM that forbids that a view calls a method in its view model.

    Assuming a public view model method like

    public void Resize(double width, double height)
    {
        ...
    }
    

    the Grid could have a SizeChanged event handler

    <Grid x:Name="GraphicGrid" SizeChanged="GraphicGrid_SizeChanged">
    ...
    

    that calls the view model method like

    private void GraphicGrid_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        ((MainWindowViewModel)DataContext).Resize(e.NewSize.Width, e.NewSize.Height);
    }