RelayCommand无法获得正确的模型

时间:2016-03-11 00:48:18

标签: c# wpf mvvm relaycommand

我创建了一个看起来像瓷砖的用户控件。创建了另一个名为TilePanel的用户控件,用作tile的默认容器。最后,看起来像Window开始屏幕的UI。我使用RelayCommand来绑定我的TileCommands

以下是代码:

Tilev2.xaml

<UserControl x:Class="MyNamespace.Tilev2"
             Name="Tile"....
>
...
    <Button x:Name="btnTile" Style="{StaticResource TileStyleButton}" Command="{Binding ElementName=Tile, Path=TileClickCommand}" >
    </Button>

</UserControl>

Tilev2.xaml.cs

    public partial class Tilev2 : UserControl
    {
        public Tilev2()
        {
            InitializeComponent();
        }
//other DPs here
        public ICommand TileClickCommand
        {
            get { return (ICommand)GetValue(TileClickCommandProperty); }
            set { SetValue(TileClickCommandProperty, value); }
        }

        // Using a DependencyProperty as the backing store for TileClickCommand.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TileClickCommandProperty =
            DependencyProperty.Register("TileClickCommand", typeof(ICommand), typeof(Tilev2));
    }
    }

然后我创建了一个TilePanel用户控件作为tile的容器

TilePanel.xaml

<UserControl x:Class="MyNamespace.TilePanel"
             ... 
             >
    <Grid>
        <ScrollViewer>
            <ItemsControl Name="tileGroup" 
                          ItemsSource="{Binding TileModels}" >
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapPanel Orientation="Horizontal"/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <local2:Tilev2 TileText="{Binding Text}"
                                           TileIcon="{Binding Icon}"
                                           TileSize="{Binding Size}"
                                           TileFontSize="{Binding FontSize}"
                                           Background="{Binding Background}"
                                           TileCaption="{Binding TileCaption}"
                                           TileCaptionFontSize="{Binding TileCaptionFontSize}"
                                           TileClickCommand="{Binding TileCommand}"
                                           />
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </ScrollViewer>
    </Grid>
</UserControl>

TilePanel.xaml.cs

public partial class TilePanel : UserControl
{
    public TilePanel()
    {
        InitializeComponent();
        DataContext = new TilePanelViewModel();
    }

    public TilePanelViewModel ViewModel
    {
        get { return (TilePanelViewModel)this.DataContext; }
    }
}

我的TilePanel ViewModel

TilePanelViewModel.cs

public class TilePanelViewModel:ViewModelBase {     private ObservableCollection _tileModels;

public ObservableCollection<TileModel> TileModels
{
    get
    {
        if (_tileModels == null)
            _tileModels = new ObservableCollection<TileModel>();

        return _tileModels;
    }
}

}

然后我的瓷砖模型

TileModel.cs

    public class TileModel : BaseNotifyPropertyChanged
    {
        //other members here
        ICommand tileCommand { get; set; }
//other properties here
 public ICommand TileCommand
        {
            get { return tileCommand; }
            set { tileCommand = value; NotifyPropertyChanged("TileCommand"); }
        }
    }
}

这是我的StartScreen视图,其中应显示带有图块的TilePanel ...

StartScreen.xaml

<UserControl x:Class="MyNamespace.StartMenu"
             ... >
<Grid>
        <DockPanel x:Name="dockPanel1" Grid.Column="0" Grid.Row="1" Margin="50,5,2,5">
            <local:TilePanel x:Name="tilePanel"></local:TilePanel>
        </DockPanel>
</Grid>
</UserControl>

StartScreen.xaml.cs

    public partial class WincollectStartMenu : UserControl, IView<StartMenuViewModel>
    {
        public WincollectStartMenu()
        {
            InitializeComponent();
        }
public StartMenuViewModel ViewModel { get { return (DataContext as StartMenuViewModel); } }

        private void UserControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            ViewModel.Tile = tilePanel.ViewModel.TileModels;
        }

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            return;
        }
}

在我的开始屏幕ViewModel中,我使用了 ObservableCollection Tile 并使用 Tile.Add(tile); 在TilePanel中使用Tiles填充我的开始屏幕...

StartMenuViewModel.cs

    TileModel tile = new TileModel() { Text = "Testing1", FontSize = 11, Size = TileSize.Medium, Background = (SolidColorBrush)new BrushConverter().ConvertFromString("#039BE5"), Tag="Something" };
    tile.TileCommand = new RelayCommand(
            p => Tile_TileClick(tile.Tag),
            p => true
        );
    temp.Add(tile);

现在问题是,如果我在下面添加新代码,则tile = new TileModel(){...} tile.TileCommand = new RelayCommand(...),即使我点击了第一个图块,我的Tile_TileClick()也会获得第二个图块的信息(或插入的最后一个图块)......

我做错了吗?或者我做错了什么......?

2 个答案:

答案 0 :(得分:1)

这不是你问题的直接答案,但希望它会给你一些想法。

好的,首先,不要像这样命名你的用户控件:

<UserControl x:Class="MyNamespace.Tilev2" Name="Tile"/>

因为在某处使用usercontrol时可以轻松覆盖名称:

<local:Titlev2 Name="SomeOtherName" />

并且Tilevs与ElementName之间的绑定不起作用:Command="{Binding ElementName=Tile, Path=TileClickCommand}"

其次,Tilev2 usercontrol的重点是什么?为什么不直接将按钮放在TilePanel类内的DataTemplate中?

如果您需要重复使用模板,可以将模板放到资源字典中。

如果您需要Tilev2代码隐藏中的某些特殊演示代码,或者您需要使用Tilev2而不使用viewmodel,那么最好创建自定义控件而不是usercontrol 在这种情况下。它有更好的设计时间支持,并且编写控件模板更容易(Triggers,DataTriggers,TempalteBinding等)。如果您使用自定义Control insead UserControl,则不必编写{Binding ElementName=Tile, Path=TileClickCommand},或使用RelativeSource等。

第三,似乎你强制MVVM模式,你可以真正利用它。 MVVM的点是与表示不同的应用程序逻辑。但是你的Tile和TilePanel用户控件只是演示文稿。您的应用程序逻辑可以在StartScreen中,这是TileName的具体用法。

我会创建名为TilePanel的自定义控件(可能继承自ItemsControl,Selector或ListBox),如果需要也可以用于Tile。两个控件都不应该知道任何视图模型。绝对没有必要这样做。

以ListBox为例。 ListBox没有viewmodel,但可以在MVVM场景中轻松使用。仅仅因为ListBox没有绑定到任何视图模型,它可以被数据绑定到任何东西。

就像ListBox创建ListBoxItems或者
一样 Combobox创建ComboBoxItems,或者 DataGrid创建DataGridRows或
GridView(在WinRT中)创建GridViewRow,你的TilePanel可以创建Tiles。

可以在TilePanel.ItemContainerStyle中指定图块特定属性(如图标或命令)的绑定,也可以在ListBox中使用类似DisplayMemberPath,resp ValueMemberPath的simillar appriach。

最终用法可能如下:

<TilePanel ItemsSource="{Bidning ApplicationTiles}" />

<TilePanel>
    <Tile Icon=".." Command=".." Text=".." />
    <Tile Icon=".." Command=".." Text=".." />
</TilePanel>

最后,名称`TilePanel&#39;诱发它是某种类型的面板,如StackPanel,WrapPanel等。换句话说,它是继承自Panel的FrameworkElement。

TilesView比TilePanel更适合控件的名称。 -View后缀不是来自MVVM,它只遵循命名约定-GridView,ListView ...

答案 1 :(得分:0)

看到问题...... 为了从按钮传递参数,我使用了CommandParameter,因此我可以在switch-case场景中使用它来知道单击了哪个按钮。但是,param仍然是空的......

<Button x:Name="btnTile" Style="{StaticResource TileStyleButton}" CommandParameter="{Binding}" Command="{Binding Path=TileClickCommand, ElementName=Tile}" >
        </Button>

TileCommand = new MyCommand() { CanExecuteFunc = param => CanExecuteCommand(), ExecuteFunc = param => Tile_TileClick(param)}

在整整两天之后,我改变了它:

由此:

<UserControl Name="Tile"...>
    <Button x:Name="btnTile" Style="{StaticResource TileStyleButton}" CommandParameter="{Binding Tag, ElementName=Tile}" Command="{Binding Path=TileClickCommand, ElementName=Tile}" >
    </Button>
</UserControl>

对此:

<UserControl Name="Tile"...>
    <Button x:Name="btnTile" Style="{StaticResource TileStyleButton}" CommandParameter="{Binding}" Command="{Binding Path=TileClickCommand, ElementName=Tile}" >
    </Button>
</UserControl>

我的第一篇文章发生了错误,因为CommandParameter不知道从哪里获取DataContext所以我将其替换为CommandParameter={Binding}所以它将从DataContext获取任何内容。