You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WPF:如何应用IMultiValueConverter实现ProgressBar颜色动态切换

First, let's fix a critical issue in your custom ProgressBar template: your PART_Indicator doesn't have a width binding tied to the Value property. Without this, the progress fill won't update as the Progress value changes. Let's address that first, then walk through both your trigger and converter approaches.

Option 1: Fixing & Validating the Style Trigger Approach

This is the more straightforward solution for your requirements, and your initial attempt is on the right track—we just need to adjust the trigger logic and add the missing width binding.

First, add a converter to calculate the indicator width based on the ProgressBar's value and actual width:

public class ProgressToWidthConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is double progress && parameter is double parentWidth)
        {
            return (progress / 100) * parentWidth;
        }
        return 0;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Now update your style with corrected triggers and the width binding. We'll use a MultiDataTrigger to handle the "IsChecked=True + Value=100" condition, and ensure the IsChecked=False trigger has priority (since it should override all other states):

<Window.Resources>
    <local:ProgressToWidthConverter x:Key="ProgressToWidthConverter"/>
    <Style x:Key="CustomProgressBar2" TargetType="ProgressBar">
        <!-- Default state: Blue track, Gray fill -->
        <Setter Property="Background" Value="Blue"/>
        <Setter Property="Foreground" Value="Gray"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ProgressBar">
                    <Grid x:Name="Root">
                        <Border Name="PART_Track" 
                                CornerRadius="0" 
                                Background="{TemplateBinding Background}" 
                                BorderBrush="{TemplateBinding Background}" 
                                BorderThickness="1"/>
                        <Border Name="PART_Indicator" 
                                CornerRadius="0" 
                                Background="{TemplateBinding Foreground}" 
                                BorderBrush="{TemplateBinding Foreground}" 
                                BorderThickness="1" 
                                HorizontalAlignment="Left">
                            <!-- Bind indicator width to ProgressBar's value and actual width -->
                            <Border.Width>
                                <MultiBinding Converter="{StaticResource ProgressToWidthConverter}">
                                    <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Value"/>
                                    <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="ActualWidth"/>
                                </MultiBinding>
                            </Border.Width>
                        </Border>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <!-- When IsChecked is TRUE AND Value reaches 100: Set fill to green -->
            <MultiDataTrigger>
                <MultiDataTrigger.Conditions>
                    <Condition Binding="{Binding IsChecked}" Value="True"/>
                    <Condition Binding="{Binding Value, RelativeSource={RelativeSource Self}}" Value="100"/>
                </MultiDataTrigger.Conditions>
                <Setter Property="Foreground" Value="Green"/>
            </MultiDataTrigger>
            <!-- When IsChecked is FALSE: Override track to red (fill stays gray, or adjust as needed) -->
            <DataTrigger Binding="{Binding IsChecked}" Value="False">
                <Setter Property="Background" Value="Red"/>
                <!-- Uncomment below if you want the fill to also change when IsChecked is False -->
                <!-- <Setter Property="Foreground" Value="DarkRed"/> -->
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Window.Resources>

This trigger approach works perfectly for your requirements:

  • Default state: Blue track, gray fill
  • When IsChecked=False: Track turns red (ignores progress value)
  • When IsChecked=True and progress hits 100: Fill turns green

Option 2: Configuring the IMultiValueConverter Approach

If you prefer using a converter for more centralized logic, here's how to set it up properly:

First, update your converter to handle both the track (Background) and fill (Foreground) colors based on the two input values:

public class ProgressToProgressBarBackgroundConverter : IMultiValueConverter {
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
        // Validate input values
        if (values.Length != 2 || 
            !(values[0] is double progress) || 
            !(values[1] is bool isChecked))
        {
            // Fallback to default colors if input is invalid
            return parameter.ToString() == "Foreground" ? Brushes.Gray : Brushes.Blue;
        }

        // Priority 1: IsChecked is False → Track red, fill stays gray (adjust as needed)
        if (!isChecked)
        {
            return parameter.ToString() == "Foreground" ? Brushes.DarkGray : Brushes.Red;
        }

        // Priority 2: IsChecked is True and progress is 100 → Fill green
        if (progress >= 100)
        {
            return parameter.ToString() == "Foreground" ? Brushes.Green : Brushes.Blue;
        }

        // Default state
        return parameter.ToString() == "Foreground" ? Brushes.Gray : Brushes.Blue;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {
        throw new NotImplementedException();
    }
}

Next, declare the converter in your resources and use MultiBinding on the ProgressBar's Background and Foreground properties:

<Window.Resources>
    <local:ProgressToProgressBarBackgroundConverter x:Key="ProgressBarColorConverter"/>
    <local:ProgressToWidthConverter x:Key="ProgressToWidthConverter"/>
    <!-- Reuse the corrected template from Option 1 -->
    <Style x:Key="CustomProgressBar2" TargetType="ProgressBar">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ProgressBar">
                    <Grid x:Name="Root">
                        <Border Name="PART_Track" 
                                CornerRadius="0" 
                                Background="{TemplateBinding Background}" 
                                BorderBrush="{TemplateBinding Background}" 
                                BorderThickness="1"/>
                        <Border Name="PART_Indicator" 
                                CornerRadius="0" 
                                Background="{TemplateBinding Foreground}" 
                                BorderBrush="{TemplateBinding Foreground}" 
                                BorderThickness="1" 
                                HorizontalAlignment="Left">
                            <Border.Width>
                                <MultiBinding Converter="{StaticResource ProgressToWidthConverter}">
                                    <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Value"/>
                                    <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="ActualWidth"/>
                                </MultiBinding>
                            </Border.Width>
                        </Border>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

<!-- Your ProgressBar with MultiBinding -->
<ProgressBar Name="progressBarColumn" 
             Minimum="0" 
             Maximum="100" 
             Value="{Binding Progress, UpdateSourceTrigger=PropertyChanged}" 
             Width="{Binding Path=Width, ElementName=ProgressCell}"
             Style="{StaticResource CustomProgressBar2}">
    <ProgressBar.Background>
        <MultiBinding Converter="{StaticResource ProgressBarColorConverter}" ConverterParameter="Background">
            <Binding Path="Progress"/>
            <Binding Path="IsChecked"/>
        </MultiBinding>
    </ProgressBar.Background>
    <ProgressBar.Foreground>
        <MultiBinding Converter="{StaticResource ProgressBarColorConverter}" ConverterParameter="Foreground">
            <Binding Path="Progress"/>
            <Binding Path="IsChecked"/>
        </MultiBinding>
    </ProgressBar.Foreground>
</ProgressBar>

Which Approach Should You Use?

  • Trigger Approach: Better for simple, declarative logic like your requirements. It's easier to adjust colors directly in XAML without modifying code.
  • Converter Approach: Better if you anticipate more complex logic (e.g., additional conditions, dynamic color values from resources) or want to centralize color logic in code.

内容的提问来源于stack exchange,提问作者Dean Movy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:45:02