MVVM模式中更改按钮内容

3

我有一个按钮,其内容如下:

 <StackPanel Orientation="Horizontal">

                <TextBlock Text="Connect"/>
                <materialDesign:PackIcon Kind="Arrow"/>
  </StackPanel>

我搜索并找到了这个:WPF按钮内容绑定但是当我有这三个元素时,即Stackpanel、PackIcon(对象)和Textblock,我不确定如何应用解决方案。
我有这个进度条,我让它出现在按钮下面:
 <ProgressBar  x:Name="XZ" Foreground="Black" Grid.Row="4" Grid.Column="1"
                  Visibility="{Binding Connecting, UpdateSourceTrigger=PropertyChanged, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"
            Value="50"
            IsIndeterminate="True" />

我希望点击按钮时,不再显示当前位置的ProgressBar,而是删除文本和PackIcon,并将ProgressBar放置在按钮中。


使用数据触发器。 - Dai
1
通常最好的做法是在所有内容上放置一个低不透明度面板,并在其中显示旋转器/忙碌指示器。当您这样做时,它会吸引用户的注意力。这样可以防止用户在可能破坏内容时点击任何东西,并且可以完全清楚地表明他们不能继续操作。 - Andy
3个回答

5
实际上,控件的更改可以使用数据触发器来完成;但在这种情况下似乎有点过头了。我只是切换两个控件的可见性即可。
<Grid>
    <StackPanel Orientation="Horizontal" Visibility="{Binding Connecting, Converter={StaticResource BooleanToCollapsedConverter}}"">

                <TextBlock Text="Connect"/>
                <materialDesign:PackIcon Kind="Arrow"/>
    </StackPanel>
    <ProgressBar  x:Name="XZ" Foreground="Black" Grid.Row="4" Grid.Column="1"
                  Visibility="{Binding Connecting, Converter={StaticResource BooleanToVisibilityConverter}}"
            Value="50"
            IsIndeterminate="True" />
</Grid>

那将是您按钮的内容。BooleanToCollapsedConverter仅是VisibiltyToBooleanConverter的反转;有多种方法可以实现此目的,这留作练习。
另外,UpdateSourceTrigger在OneWay绑定中没有任何意义(它不会更新源!),并且在可见性上甚至不需要它,因为用户无法更改该输入。

那你的建议是我只需隐藏控件并显示进度条?我不确定为什么我之前没有想到这个,感觉很愚蠢。非常感谢!谢谢。 - Black Panther
@BradleyDotNet,我可以问一下为什么您会使用trigger而不是DataTemplate吗? - 3xGuy
@3xGuy 数据模板不允许您在标志上切换;最多只能在类型上。您使用额外类设置的系统似乎比触发器更过度。 - BradleyDotNET
1
@BradleyDotNet 所以我的理解是,我所做的并不是错误的,只是有点过度设计了? - 3xGuy
1
@3xGuy 在我看来,是的,那似乎有些过度了。扩展你的概念意味着你在程序中为每个可能的UI控件状态都有一个类。这将非常难以快速管理。我所做的或者使用DataTrigger对我来说似乎是正确的答案。 - BradleyDotNET
1
@BradleyDotNet,谢谢你。我在这里回答问题是为了变得更好,并尝试将我的答案与其他人进行比较。感谢你的意见。 - 3xGuy

1
您可以使用数据模板。类似以下内容: XAML:
    <Window.Resources>
    <DataTemplate DataType="{x:Type local:ButtonInfo}">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
            </Grid.RowDefinitions>
            <Label Grid.Row="0" Content="Press me"></Label>
            <Label Grid.Row="1" Content="{Binding Label}"></Label>
        </Grid>
    </DataTemplate>
    <DataTemplate DataType="{x:Type local:ProgressInfo}">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
            </Grid.RowDefinitions>
            <ProgressBar Height="30" Value="{Binding Progress}"></ProgressBar>
            <Label Grid.Row="1" Content="{Binding Label}"></Label>
        </Grid>
    </DataTemplate>
</Window.Resources>


   <Grid>
        <Button Command="{Binding ProcessCommand}" Content="{Binding ButtonInfo}">            
        </Button>
    </Grid>

C#:

    public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new MainWindowViewModel();
    }
}

public class ViewModelBase:INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class MainWindowViewModel:ViewModelBase
{
    public MainWindowViewModel()
    {
        ButtonInfo = new ButtonInfo(){Label = "Button Info"};
        ProcessCommand = new DelegateCommand(Process);
    }
    private ButtonInfo _buttonInfo;
    public ButtonInfo ButtonInfo
    {
        get { return _buttonInfo; }
        set
        {
            _buttonInfo = value;
            OnPropertyChanged();
        }
    }

    public DelegateCommand ProcessCommand { get; set; }

    private async void Process()
    {
        ButtonInfo = new ProgressInfo(){Label = "Progress Info"};
        await ProcessAsync();
    }

    private Task ProcessAsync()
    {
        return Task.Run(() =>
        {
            for (int i = 0; i < 100; i++)
            {
                Application.Current.Dispatcher.Invoke(() =>
                {
                    ButtonInfo.Progress = i;
                    if (i==99)
                    {
                        ButtonInfo = new ButtonInfo(){Label = "Button Again"};
                    }
                });
                Thread.Sleep(100);
            }
        });
    }
}

public class ButtonInfo:ViewModelBase
{
    private string _label;
    private int _progress;
    private bool _isProcessing;

    public string Label
    {
        get { return _label; }
        set
        {
            _label = value;
            OnPropertyChanged();
        }
    }

    public int Progress
    {
        get { return _progress; }
        set
        {
            _progress = value;
            OnPropertyChanged();
        }
    }

    public bool IsProcessing
    {
        get { return _isProcessing; }
        set
        {
            _isProcessing = value;
            OnPropertyChanged();
        }
    }
}

public class ProgressInfo : ButtonInfo { }

-1
您可以创建一个按钮模板来实现此功能,然后在需要带有加载效果的按钮的任何地方重复使用该模板:
    <Button Width="120" Height="40" Tag="False" Name="loadingButton" Click="loadingButton_Click">
    <Button.Template>
        <ControlTemplate>
            <Border Name="PART_Border" BorderBrush="Black" BorderThickness="1" CornerRadius="2" Background="Transparent">
                <Grid Name="PART_Root">
                    <TextBlock Name="PART_Text" HorizontalAlignment="Center" VerticalAlignment="Center">Data</TextBlock>
                    <ProgressBar  IsIndeterminate="True"  Name="PART_Loading"></ProgressBar>
                </Grid>
            </Border>
            <ControlTemplate.Triggers>
                <Trigger Property="Tag" Value="True">
                    <Setter TargetName="PART_Text" Property="Visibility" Value="Collapsed"></Setter>
                    <Setter TargetName="PART_Loading" Property="Visibility" Value="Visible"></Setter>
                </Trigger>

                <Trigger Property="Tag" Value="False" >
                    <Setter TargetName="PART_Text" Property="Visibility" Value="Visible"></Setter>
                    <Setter TargetName="PART_Loading" Property="Visibility" Value="Collapsed"></Setter>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </Button.Template>
</Button>
    private async void loadingButton_Click(object sender, RoutedEventArgs e)
    {
        loadingButton.Tag = true.ToString();//display loading
        await Task.Run(() => { Thread.Sleep(4000); });//fake data loading
        loadingButton.Tag = false.ToString();//hide loading
    }

请注意,如果您使用MVVM模式,则还可以绑定视图模型中的属性到Tag属性。

1
似乎使用DataTrigger比滥用Tag更好。 - BradleyDotNET
1
使用这样的模板似乎是合理的,但我认为Tag不是最好的属性。也许可以派生一个带有附加属性的用户控件?(例如称为StatefulButton?) - Zarenor
通常我会创建自定义控件或至少一个用户控件来完成这项任务,但是如果标签没有被使用,对于初学者在WPF内理解和应用起来更容易。 - Mahmoud Heretani
也许使用“isEnabled”会是个好主意。如果在此过程运行时该按钮没有被禁用,那么如果用户再次点击它会发生什么呢?通常情况下这将是一件糟糕的事情。 - Andy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接