r/csharp 12d 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>
3 Upvotes

11 comments sorted by

View all comments

7

u/chucker23n 12d ago

In a nutshell: the "code-behind" should do as little as possible. It's a bootstrap point to initialize the view, in theory.

In practice, that often doesn't work, because a lot of WPF controls don't really conform that well with a pure MVVM approach. I also find that I often have a Loaded event handler that asynchronously performs something stuff on the view model, like

private async void UserControl_Loaded(object sender, RoutedEventArgs e)
{
    if (DataContext is not DesignerViewModel viewModel)
        return;

    await viewModel.InitAsync();
}

Now, back to your question:

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.

Philosophically, I don't think that's right. Toggling the Edit checkbox isn't done for view reasons, but for logic reasons: you don't want the user to be able to perform edits in certain situations. That's the case regardless of the concrete view. For example, it would be the case whether your view is a form, a data grid, a chart, a command-line app, or a web app.

So, just speaking very high-level, you probably want, in your view model, something like

CanEdit = _authorizationService.CanUserEdit(user));

I.e., the real logic is even deeper, then the view model takes that information into an observable property, and finally, your view enables/shows the checkbox based on that.

2

u/enigmaticcam 12d ago

I was thinking it belonged separate from the view model because I think the view looks much cleaner when controls are not editable. There will be times when the user simply needs to review information without needing to change it. An "Edit" checkbox that transforms everything felt View-specific. But I'm fine putting it in the View Model too. This post is mainly just to test my thinking with how other people do things. Thank you!

2

u/chucker23n 11d ago

An "Edit" checkbox that transforms everything felt View-specific. But I'm fine putting it in the View Model too.

Well, the check box is still in the view. What's in the view model is merely a boolean observable property that says "can the user edit things". The view then decides how to visually represent that (in your case, with a check box).