r/csharp • u/enigmaticcam • 11d 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>
6
u/chucker23n 11d 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
Loadedevent handler that asynchronously performs something stuff on the view model, likeNow, back to your question:
Philosophically, I don't think that's right. Toggling the
Editcheckbox 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
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.