【问题标题】:Binding buttons created at run time with MVVM使用 MVVM 在运行时创建的绑定按钮
【发布时间】:2020-03-09 22:20:49
【问题描述】:

我的申请如下:

型号:

贸易

观看次数:

TradeManagerView

TradeView

视图模型:

TradeManagerViewModel

TradeManagerView 有一个DataGrid,其ItemsSource 在TradeManagerViewModel 中设置为ObservableCollection<Trade>,其数据模板是TradeView 自定义用户控件。

Trade 模型有一个TradeDirection 属性,它可以是In 或Out。 TradeManagerViewModel 提供两组ObservableCollection<Button>,每个TradeDirection 一组。这些按钮由用户在运行时配置,因此不在 XAML 中创建它们。我正在努力将TradeView 中的ItemsControl 绑定到TradeManagerViewModel 中的按钮,因为它的数据上下文是Trade 的一个实例,它由DataGrid ItemsSource 提供。

我已经尝试了以下方法,但都没有成功。我正在使用 Caliburn.Micro,它在运行时将 View 的 DataContext 设置为 ViewModel。

<ItemsControl x:Name="TradeActions" ItemsSource="{Binding InboundTradeActions, ElementName=TradeManager}">

<ItemsControl x:Name="TradeActions" ItemsSource="{Binding InboundTradeActions, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}">

如前所述,我需要根据Trade.TradeDirection 绑定到正确的集合(即InboundTradeActions 或OutboundTradeActions),因此上述方法在任何情况下都不起作用。我不确定这在带有 DataTrigger 的 XAML 中是否最有效?或创建返回正确集合的第三个属性。但不知何故,该属性需要知道 TradeDirection 用于相对 TradeView 的 DataContext。

我希望这一切都有意义。如果没有请告诉我,我会修改它。

贸易类

internal class Trade : PropertyChangedBase
{
    public enum Direction { In, Out };

    private ObservableCollection<Button> tradeActions = new ObservableCollection<Button>();
    public ObservableCollection<Button> TradeActions
    {
        get { return tradeActions; }
        set
        {
            tradeActions = value; 
            NotifyOfPropertyChange();
        }
    }

    public Direction TradeDirection
    {
        get { return tradeDirection; }
        set
        {
            tradeDirection = value;

            if (value == Direction.In)
            {
                InArrowVisibility = Visibility.Visible;
                OutArrowVisibility = Visibility.Hidden;
            }
            else
            {
                InArrowVisibility = Visibility.Hidden;
                OutArrowVisibility = Visibility.Visible;
            }

            NotifyOfPropertyChange();
        }
    }

    public Visibility InArrowVisibility { get; set; }
    public Visibility OutArrowVisibility { get; set; }

    // Other properties removed to shorten
}

TradeManagerView

<Window x:Class="ExaltedTrade.Views.TradeManagerView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:ExaltedTrade.Views"
    mc:Ignorable="d"
    Title="Trade Manager" Height="450" Width="400"
    WindowStartupLocation="Manual"
    Style="{StaticResource TradeManagerWindow}"
    Top="{Binding WindowTop, Mode=TwoWay}"
    Left="{Binding WindowLeft, Mode=TwoWay}"
    x:Name="TradeManager">
<Grid>
    <DataGrid x:Name="ActiveTrades"
              AutoGenerateColumns="False"
              IsReadOnly="True"
              HeadersVisibility="None"
              GridLinesVisibility="None"
              Style="{StaticResource TradeManagerDataGrid}"
              CellStyle="{StaticResource TradeControlCell}"
              ItemsSource="{Binding ActiveTrades}">
        <DataGrid.Columns>
            <DataGridTemplateColumn Width="*">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <local:TradeView /> <!-- This is the custom UserControl -->
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
</Grid>
</Window>

贸易视图

我已经包含了 UserControl 的层次结构,但删除了任何多余的控件以缩短。

<UserControl x:Class="ExaltedTrade.Views.TradeView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:ExaltedTrade.Views"
         mc:Ignorable="d" 
         d:DesignHeight="150" d:DesignWidth="500" Style="{StaticResource TradeView}">
<Grid>
    <Expander x:Name="Expander" HorizontalAlignment="Stretch" Margin="0,0,0,0" VerticalAlignment="Top" IsExpanded="True">
        <Expander.Header>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="2*" /><!-- Player Name -->
                    <ColumnDefinition Width="14" /><!-- Trade Direction -->
                    <ColumnDefinition Width="1*" /><!-- Quantity -->
                    <ColumnDefinition Width="16" /><!-- Currency Icon -->
                    <ColumnDefinition Width="26" /><!-- Timer -->
                    <ColumnDefinition Width="26" /><!-- Invite -->
                    <ColumnDefinition Width="26" /><!-- Trade -->
                    <ColumnDefinition Width="26" /><!-- Kick -->
                    <ColumnDefinition Width="26" /><!-- Whisper -->
                    <ColumnDefinition Width="26" /><!-- Close -->
                </Grid.ColumnDefinitions>

                <!-- Some controls removed to shorten -->

        </Expander.Header>
        <Expander.Content>
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="2*" />
                    <ColumnDefinition Width="1*" />
                    <ColumnDefinition Width="16" />
                    <ColumnDefinition Width="22" />
                </Grid.ColumnDefinitions>

                <!-- Some controls removed to shorten -->

<!-- BINDING IS HERE -->
                <ItemsControl x:Name="TradeActions" Grid.Column="0" Grid.Row="1" ItemsSource="{Binding InboundTradeActions, ElementName=TradeManager}">
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>
                            <WrapPanel Style="{StaticResource TradeActions}" IsItemsHost="True" />
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
                </ItemsControl>
            </Grid>
        </Expander.Content>
    </Expander>
</Grid>
</UserControl>

TradeManagerViewModel

class TradeManagerViewModel : PropertyChangedBase, IHandle<string>
{

    private ObservableCollection<Trade> activeTrades = new ObservableCollection<Trade>();
    public ObservableCollection<Trade> ActiveTrades
    {
        get
        {
            return activeTrades;
        }
        set
        {
            activeTrades = value;
            NotifyOfPropertyChange();
        }
    }

    public static ObservableCollection<Button> InboundTradeActions { get; set; } = new ObservableCollection<Button>();
    public static ObservableCollection<Button> OutboundTradeActions { get; set; } = new ObservableCollection<Button>();

    public TradeManagerViewModel()
    {
        //TODO: Remove the test Trades
        ObservableCollection<Trade> temp = new ObservableCollection<Trade> { new Trade("ExamplePlayer", Trade.Direction.In) };
        temp.Add(new Trade("AnotherExamplePlayer", Trade.Direction.Out));
        ActiveTrades = temp;

        SetupTradeActions();
    }

    private void SetupTradeActions()
    {
        // TradeActions for inbound Trades
        InboundTradeActions.Clear(); // Remove any existing TradeAction buttons just in case
        foreach (TradeAction tAction in AppSettings.GetSingleton().InboundTradeActions)
        {
            Button action = new Button
            {
                Content = tAction.ButtonText,
                //action.Command = ; //TODO: Need to implement an ICommand to link the method to the buttons
                CommandParameter = tAction
            };

            InboundTradeActions.Add(action);
        }

        // TradeActions for outbound Trades
        OutboundTradeActions.Clear(); // Remove any existing TradeAction buttons just in case
        foreach (TradeAction tAction in AppSettings.GetSingleton().OutboundTradeActions)
        {
            Button action = new Button
            {
                Content = tAction.ButtonText,
                //action.Command = ; //TODO: Need to implement an ICommand to link the method to the buttons
                CommandParameter = tAction
            };

            OutboundTradeActions.Add(action);
        }
    }
}

【问题讨论】:

  • “TradeManagerViewModel 提供了两组ObservableCollection&lt;Button&gt;”——好吧,这就是你的问题所在。 Button 是一个视图对象。任何视图模型都不应该与一个视图模型相关。正确的做法是将视图模型中的按钮抽象为一些非视图类型的对象,并将 that 绑定到 XAML 中的某个视图,例如带有适当模板的ItemsControl。您提出的问题过于宽泛,但已经有很多类似的问题,以及您最喜欢的网络搜索引擎的发现示例。

标签: c# wpf mvvm data-binding caliburn.micro


【解决方案1】:

使用Style 和DataTrigger 应该可以。像这样的:

<ItemsControl x:Name="TradeActions" Grid.Column="0" Grid.Row="1">
    <ItemsControl.Style>
        <Style TargetType="ItemsControl">
            <Setter Property="ItemsSource" Value="{Binding DataContext.InboundTradeActions, 
                        RelativeSource={RelativeSource AncestorType=Window}}" />
            <Style.Triggers>
                <DataTrigger Binding="{Binding TradeDirection}" Value="Out">
                    <Setter Property="ItemsSource" Value="{Binding DataContext.OutboundTradeActions, 
                                RelativeSource={RelativeSource AncestorType=Window}}" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </ItemsControl.Style>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel Style="{StaticResource TradeActions}" IsItemsHost="True" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

【讨论】:

    【解决方案2】:

    您的问题是 SetupTradeActions 中的 command.Action 对吧? 如果是这样:

    您可以在 Viewmodel 中定义 ICommand 并在代码中创建与 Button 的 CommandProperty 的绑定。 比如:

    public class myViewModel
    {
      public myViewModel()
      {
          InBoundCommand = new InboundCommand ();
          Button button = new Button();
          button.Content = "123";
          Binding b = new Binding("InBoundCommand");
          button.SetBinding(Button.CommandProperty, b);
      }
        public ICommand InBoundCommand {get;}
    }
    
    public class InboundCommand : ICommand
    {
      public event EventHandler CanExecuteChanged;
    
    
      public bool CanExecute(object parameter)
      {
        return true; //or whatever you need here
      }
    
      public void Execute(object parameter)
      {
        //Do fancy Stuff;
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-06-27
      • 1970-01-01
      • 2020-11-16
      • 2017-10-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-10
      相关资源
      最近更新 更多