How do I properly change the state of a control in WPF using data binding?

I am very new to data binding in WPF.

Let's say I have a class called FileSource

which has one property File

(string) and some other properties derived from that. In my GUI, I have a control that needs to change its appearance between two "modes": one mode if File

present null

, another mode if not null

. Let's say that one mode sets the visibility of some child components to Visible

and others to Collapsed

, while another mode does the opposite.

I can think of 3 ways to get around this:

  • In, FileSource

    create a different type property Visibility

    and return the correct visibility for each control. But that sounds really bad to me - it looks like I'll be carefully mixing "model" ( FileSource

    ) with the behavior of the view (control).
  • Create a bunch of trivial data transformation classes, then bind the data to a semantic property of the model ( File

    in this case). For example, a converter string

    Visibility

    for some components and a different converter string

    Visibility

    (which returns "opposite" Visibility

    ) for other components. This works and plays well with property change notifications, but creating a new class for every kind of different responses I expect from helper sounds is useless to me.
  • Write a method Update

    and subscribe to the event PropertyChanged

    . It sounds like I am pretty much beating the data binding point to me.

What is the correct way to do this? Is there perhaps an easy way to do the inline data conversion to XAML (for the value I intend to read, but not write back to the source)?

+3


source to share


4 answers


You don't need too many converter classes. You only need one BoolToVisibilityConverter

, but with properties that define the visibility values ​​for true

and false

. You create instances like this:

<BoolToVisibilityConverter x:Key="ConvertBoolToVisible"
    FalseVisibility="Collapsed" TrueVisibility="Visible" />
<BoolToVisibilityConverter x:Key="ConvertBoolToVisibleInverted"
    FalseVisibility="Visible" TrueVisibility="Collapsed" />

      

Another converter IsNullConverter

. You can parameterize it using a type property bool InvertValue

. In your resource dictionary, instances can be named ConvertIsNull

and ConvertIsNotNull

. Or you can create two classes if you like.

Finally, you can connect converters using ChainConverter

, which combines multiple value converters. You can find an example implementation in my private framework ( permalink ), With it you can instantiate a converter in XAML for example ConvertIsNotNullToVisibleInverted

. Using example:



<a:ChainConverter x:Key="ConvertIsNotNullToVisible">
    <a:ValueConverterRef Converter="{StaticResource ConvertIsNotNull}"/>
    <a:ValueConverterRef Converter="{StaticResource ConvertBoolToVisible}"/>
</a:ChainConverter>

      

An alternative is to use triggers. The XAML will be more complex, so I prefer converters. This requires writing some classes, but it's worth it. With such an architecture, you don't need dozens of classes for each combination, and both C # and XAML code will be simple and readable.

And don't add every possible combination of transducers. Add them only when you need them. Most likely, you only need a few.

+3


source


Consider using visual states - they are for the scenario you are talking about where you have a control that needs to transition between multiple states. One of the benefits of using this approach in bindings is that it allows animations (including transitions).


To make it work, you declare your visual state groups and visual states below the root of your control:

<UserControl>
    <Grid x:Name="LayoutRoot">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="DefaultStates">
                <VisualState x:Name="State1" />
                <VisualState x:Name="State2">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="textBlock2"
                                                       Storyboard.TargetProperty="Visibility">
                            <DiscreteObjectKeyFrame KeyTime="0" To="Visible" />
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>

        <TextBlock x:Name="textBlock1" Text="state #1" />
        <TextBlock x:Name="textBlock2" Text="state #2" Visibility="Collapsed" />

    </Grid>
</UserControl>

      



To transition between states, you can call VisualStateManager.GoToState(this, "State2", true)

. You can also use the Blend SDK to navigate through triggers from XAML. Probably the most useful way to navigate is to use DataStateBehavior

one that binds states to the view-model property:

    <Grid x:Name="LayoutRoot">
        <i:Interaction.Behaviors>
            <ei:DataStateBehavior Binding="{Binding CurrentState}" 
                                  Value="State2" 
                                  TrueState="State2" FalseState="State1" />
        </i:Interaction.Behaviors>

      

This way you can just update a property in your viewmodel and the state of the UI will update automatically.

public string File
{
    get { return _file; }
    set
    {
        _file = value;
        RaisePropertyChanged();
        RaisePropertyChanged(() => CurrentState);
    }
}
private string _file;

public string CurrentState
{
    get { return (File == null ? "State1" : "State2"); }
}

      

+2


source


Option (2) is what you are doing here. You will need IValueConverter

(or 2, depending on the implementation).

I would call it NullToVisibilityConverter

or something like that. It will return Visiblity.Visible

if the argument is value

not null, but Visibility.Collapsed

if it is. To change the behavior, you can simply write a second converter or use the ConverterParameter.

It will look like this:

public class NullToVisibilityConverter : IValueConverter
{
   public object Convert(...)
   {
       return value != null ? Visibility.Visible : Visibility.Collapsed;
   }

   public object ConvertBack(...)
   {
       return Binding.DoNothing;
   }
}

      

Using:

<Button Visibility="{Binding File, Converter={StaticResource MyConverter}"/>

      

+1


source


And ... here's another way to use styles / triggers:

MainWindow.xaml

<Window x:Class="WpfApplication19.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <StackPanel>
            <StackPanel.Resources>
                <Style TargetType="TextBlock" x:Key="FileIsNull">
                    <Setter Property="Visibility" Value="Collapsed" />
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding File}" Value="{x:Null}">
                            <Setter Property="Visibility" Value="Visible" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
                <Style TargetType="TextBlock" x:Key="FileIsNotNull">
                    <Setter Property="Visibility" Value="Visible" />
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding File}" Value="{x:Null}">
                            <Setter Property="Visibility" Value="Collapsed" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </StackPanel.Resources>
            <TextBlock Text="Filename is null" Style="{StaticResource FileIsNull}" MinHeight="50" Background="Beige" />
            <TextBlock Text="{Binding File}" Style="{StaticResource FileIsNotNull}" MinHeight="50" Background="Bisque" />

            <Button Name="btnSetFileToNull" Click="btnSetFileToNull_Click" Content="Set File To Null" Margin="5" />
            <Button Name="btnSetFileToNotNull" Click="btnSetFileToNotNull_Click"  Content="Set File To Not Null" Margin="5" />
        </StackPanel>
    </Grid>
</Window>

      

MainWindow.xaml.cs

using System.ComponentModel;
using System.Windows;

namespace WpfApplication19
{
    public partial class MainWindow : Window
    {
        public FileSource fs { get; set; }

        public MainWindow()
        {
            InitializeComponent();
            fs = new FileSource();
            this.DataContext = fs;
        }

        private void btnSetFileToNull_Click(object sender, RoutedEventArgs e)
        {
            fs.File = null;
        }

        private void btnSetFileToNotNull_Click(object sender, RoutedEventArgs e)
        {
            fs.File = @"C:\abc.123";
        }
    }

    public class FileSource : INotifyPropertyChanged
    {
        private string _file;
        public string File { get { return _file; } set { _file = value; OnPropertyChanges("File"); } }

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanges(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

      

+1


source







All Articles