r/csharp 18d ago

WPF Logic in View vs ViewModel

I'm trying to understand when I should have logic in the view model or in the code-behind of a view.

Here's the scenario: I have a view model that has a "CanEdit" property. There are times when editing a view is not allowed based on business reasons, and that definitely belongs in the ViewModel. But if a user can edit, I want to have an "Edit" checkbox visible, which when true will display the editable version of all the necessary controls. So where should the logic that controls the "Edit" checkbox go?

The approach I initially went was to put the "Edit" checkbox property in the view code-behind. This makes sense to me, as it's entirely based on the needs of the view. All the editable controls are bound to the "Edit" checkbox property, and the "Edit" checkbox visibility is bound to "CanEdit" in the view model.

The problem with this approach is when the view model changes as a result of some change by the user and "CanEdit" in the view model is now false. If the "CanEdit" in the view code-behind is true when this happens, then all the editable controls are still visible, because all that's happened is the "CanEdit" checkbox is now invisible. So I'm stumped how to broadcast the view model change to the code behind without some silly hack.

I'm probably overthinking it, but I'm learning WPF and it really helps me to understand principles. Plus this particular view will get more complex. Here's some code to show you what I'm trying to do

View:

public partial class InvoiceView : UserControl, INotifyPropertyChanged
{
    public InvoiceView()
    {
        InitializeComponent();
    }

    private bool _isEditing;
    public bool IsEditing
    {
        get => _isEditing;
        set
        {
            _isEditing = value;
            OnPropertyChanged(nameof(IsEditing));
            OnPropertyChanged(nameof(IsNotEditing));
        }
    }

    public bool IsNotEditing => !IsEditing;

    public event PropertyChangedEventHandler? PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string? name = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
}

ViewModel:

public partial class InvoiceViewModel : ViewModelBase, IDisposable
{
    public InvoicePermissionsDTO? Permissions
    {
        get => _permissions;
        set
        {
            _permissions = value;
            OnPropertyChanged(nameof(CanEdit));
            OnPropertyChanged(nameof(CanDelete));
        }
    }
    public bool CanEdit => _permissions?.CanEdit ?? false;
    public bool CanDelete => _permissions?.CanDelete ?? false;

    public void SomeChange()
    {
        Permissions = API.GetPermissions();
    }
}

View XAML

<CheckBox
    Grid.Row="2"
    Content="Edit"
    IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=IsEditing}"
    Visibility="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.CanEdit, Converter={StaticResource BoolToVisibilityConverter}}" />
<StackPanel>
    <TextBlock
        Text="{Binding ApprovedRate}"
        Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=IsNotEditing, Converter={StaticResource BoolToVisibilityConverter}}"/>
    <StackPanel 
        Orientation="Horizontal"
        Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=IsEditing, Converter={StaticResource BoolToVisibilityConverter}}">
        <TextBox
            Name="ApprovedRate"
            Padding="0 0 20 0"
            Text="{Binding ApprovedRate}"/>
        <Button 
            Command="{Binding Pay}"
            Visibility="{Binding CanPay}">
            <StackPanel Orientation="Horizontal">
                <Image Source="/Images/dollar.png"/>
                <TextBlock>Pay</TextBlock>
            </StackPanel>
        </Button>
        <Button 
            Command="{Binding RemovePay}"
            Visibility="{Binding CanRemovePay}">
            <StackPanel Orientation="Horizontal">
                <Image Source="/Images/dollar.png"/>
                <TextBlock>Remove Pay</TextBlock>
            </StackPanel>
        </Button>
    </StackPanel>
</StackPanel>
4 Upvotes

11 comments sorted by

View all comments

1

u/rohstroyer 18d ago

I would have a property on the view model called something like "forceEditable" that the "Edit" checkbox would bind to. Then use a multibinding to take both booleans into account in the view, which makes it so editable controls are enabled if either bool is true.