Jagd
Jagd

Reputation: 7306

How to dynamically set style of an element in a User Control

In a WPF project, I have a user control (Valve.xaml) that defines a Polygon shape.

<Grid>
   <Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Style="{StaticResource Valve_Open}"/>
</Grid>

I am displaying the Valve user control in a window xaml (FFG.xaml) file, like such:

<Window
   <!-- removed other namespaces for brevity -->
   xmlns:cl="clr-namespace:FFG.Controls;assembly=PID.Controls">
   <Grid>
      <cl:Valve x:Name="valve201A"></cl:Valve>
   </Grid>
</Window>

I am setting the DataContext of FFG.xaml to class FFG_ViewModel.cs, and it contains an instance of the Valve_Model class. Valve_Model essentially represents the valve that is drawn on the window in FFG.xaml.

public class FFG_ViewModel : ViewModelBase {

   public Valve_Model Valve201A { get; set; }

   // There are other properties and methods, but I've removed them for brevity also

}

Here is the Valve_Model class:

public class Valve_Model  INotifyPropertyChanged {

        public event PropertyChangedEventHandler PropertyChanged;

        private bool _isValveOpen { get; set; }
        public bool IsValveOpen {
            get {
                return _isValveOpen;
            }
            set {
                _isValveOpen = value;
                OnPropertyChanged("IsValveOpen");
            }
        }

        #region INotifyPropertyChanged
        protected virtual void OnPropertyChanged(string propertyName) {
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null) {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }
        #endregion

    }

QUESTION: What I want is for the Style property in the Valve.xaml to change when the IsValveOpen property changes.

So if the valve is open then it would be:

<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Style="{StaticResource Valve_Open}"/>

and when the property is changed to false then I need the style of the polygon to be changed to:

<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Style="{StaticResource Valve_Closed}"/>

How do I go about implementing this exactly?

Upvotes: 0

Views: 1382

Answers (3)

mm8
mm8

Reputation: 169330

Instead of setting the actual Style property to a new value, you could add a DataTrigger to the Style itself that changes the properties of the Polygon based on the value of the IsValveOpen source property.

Valve.xaml:

<Grid>
    <Polygon Name="pValve" Points="0,50 0,20 50,50 50,20">
        <Polygon.Style>
            <Style TargetType="Polygon">
                <!-- Copy the setters from the Valve_Closed style here -->
                <Setter Property="Fill" Value="Red" />
                <Style.Triggers>
                    <DataTrigger Binding="{Binding IsValveOpen}" Value="True">
                        <!-- Copy the setters from the Valve_Open style here -->
                        <Setter Property="Fill" Value="Red" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Polygon.Style>
    </Polygon>
</Grid>

FFG.xaml:

<Grid>
    <cl:Valve x:Name="valve201A" DataContext="{Binding Valve201A}" />
</Grid>

Upvotes: 0

bab7lon
bab7lon

Reputation: 343

You could use an IMultiValueConverter.

First, let's simplify the use case. Basicly you want to swap Styles based on a given state object, which I'll represent by a ToggleButton. The fact that you're wrapping everything in a UserControl also has no influence on the underlying concept.

Demo:

  • Starting a fresh project
  • Declaring our Resources
  • Feeding the Window and the state to the Converter.

MainWindow.xaml

<Window 

    ...

    >
    <Window.Resources>
        <local:ToStyleConverter x:Key="ToStyleConverter"/>
        <Style x:Key="Valve_Open" TargetType="{x:Type Polygon}">
            <Setter Property="Fill" Value="Red"/>
        </Style>
        <Style x:Key="Valve_Closed" TargetType="{x:Type Polygon}">
            <Setter Property="Fill" Value="Green"/>
        </Style>
    </Window.Resources>
    <DockPanel>
        <ToggleButton x:Name="butt" DockPanel.Dock="Bottom">Switch</ToggleButton>
        <Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Stretch="Uniform">
            <Polygon.Style>
                <MultiBinding Converter="{StaticResource ToStyleConverter}">
                    <Binding RelativeSource="{RelativeSource FindAncestor, 
                                              AncestorType={x:Type Window}}"/>
                    <Binding ElementName="butt" Path="IsChecked"/>
                </MultiBinding>
            </Polygon.Style>
        </Polygon>
    </DockPanel>
</Window>

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
}

public class ToStyleConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, 
        object parameter, System.Globalization.CultureInfo culture)
    {
        if (values[0] is Window)
        {
            Window win = (Window)values[0];
            if ((bool)values[1])
                return win.FindResource("Valve_Open");
            if (!(bool)values[1])
                return win.FindResource("Valve_Closed");
        }
        return DependencyProperty.UnsetValue;
    }

    public object[] ConvertBack(object values, Type[] targetType,
        object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Changing to any specific use case means:

  • Pointing the relativesource binding to the Control that holds the Resources (Styles)
  • Using the second binding to add the state to the Converter (DP/INPC)
  • Implementing Converter logic

Upvotes: 1

rmojab63
rmojab63

Reputation: 3631

You can (should as much as I know) use a DataTrigger, within a ControlTemplate. Assuming that these two are your Styles:

<Window.Resources>
    <Style TargetType="Polygon" x:Key="Valve_Open">
        <Setter Property="Fill" Value="Red"/>
    </Style>
    <Style TargetType="Polygon" x:Key="Valve_Close">
        <Setter Property="Fill" Value="Green"/>
    </Style>
</Window.Resources>

You should add this style to the resources:

 <Style x:Key="changeStyle" TargetType="Control">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Control">
                    <Grid>
                        <Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Style="{StaticResource Valve_Open}" />
                    </Grid>
                    <ControlTemplate.Triggers>
                        <DataTrigger Binding="{Binding Valve201A.IsValveOpen}" Value="true">
                            <Setter TargetName="pValve" Property="Style" Value="{StaticResource Valve_Close}" />
                        </DataTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

and use them in your views:

 <Control DataContext="{Binding}" Style="{StaticResource changeStyle}" />

Upvotes: 0

Related Questions