【WPF】CommandParameter解決多傳參問題


方法一:傳參按鈕控件自身綁定的ItemSource

用WAF框架實現MVVM,按鈕的點擊事件都要通過Command來傳遞到這個View對應的ViewModel上,再通過ViewModel傳遞到上層的Controller層,在Controller層通過DelegateCommand處理按鈕真正的事件。有時候需要給該Command附加上一些參數(CommandParameter),但是默認CommandParameter只能傳遞一個參數。谷歌搜到的解決方法很復雜,於是想了個辦法CommandParameter參數傳遞的是這個按鈕控件自身綁定的ItemSource,然后通過從ItemSource身上的DataContext來拿到數據,再截取字符串分割得到想要的部分數據(或者強轉為ItemSource對應的實體類)。

正常情況下,Button的綁定:

<Button Command="{Binding BtnCommand}" />

這個Command會沿着View –> ViewModle –> Controller層傳遞。

如果這個Button是ListBox的Item,這個ListBox的Item要使用數據模板,且ItemsSource綁定到了這組Button的數據源,是這樣綁:

<ListBox x:Name="listBox" ItemsSource="{Binding DataList}" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid x:Name="grid">
                <local:WaitingProgress/>
                <Button Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=DataContext.BtnCommand}" CommandParameter="{Binding RelativeSource={x:Static RelativeSource.Self}}"> <!-- 傳參是Button控件綁定的ItemsSource,即這里是DataList列表 -->
                    <Image Tag="{Binding id}" x:Name="img" Stretch="UniformToFill" Width="150" Height="150" webImg:ImageDecoder.Source="{Binding icon}">
                    </Image>
                </Button>
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>

    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel Name="wrapPanel" HorizontalAlignment="Stretch" />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
</ListBox>

這個ItemSource綁定的DataList是ViewModle中的一個實例列表。ViewModel關鍵代碼如下:

private ICommand refreshDesignCommand; // 向上傳遞這個Command:View-->ViewModel-->Controller
public ICommand RefreshDesignCommand
{
    get { return refreshDesignCommand; }
    set { SetProperty(ref refreshDesignCommand, value); }
}

private ObservableCollection<GoodsJsonData> dataList = null;
public ObservableCollection<GoodsJsonData> DataList
{
    get { return dataList; }
    set { dataList = value; }
}

實體類:

public class GoodsJsonData
{
    public string id { get; set; }      // 還可用於圖片被點擊調時,標記出是哪個縮略圖被點擊
    public string icon { get; set; }    // 縮略圖
    public string image { get; set; }   // 大圖
    public string model { get; set; }   // 該商品對應的模型XML

    public override string ToString()
    {
        return "id = " + id + " , icon = " + icon + " , image = " + image + " , model = " + model;
    }
}

Controller層的關鍵代碼:

private readonly DelegateCommand refreshDesignCommand;  // 縮略圖的點擊回調

[ImportingConstructor]
public WebImageController()
{
    this.refreshDesignCommand = new DelegateCommand(p => RefreshDesignCommand((Button)p));
}

private void RefreshDesignCommand(Button btn)
{
    // 方法一:將DataContext打印字符串,截取出目標數據
    string dataContext = btn.DataContext.ToString();
    System.Console.WriteLine(dataContext);          // id = 000201 , icon = http://192.168.1.222/mjl/4-01.png , image = 2/造型/4-01.png , model = xml/qiang07.xml
    // 截取字符串來獲得目標數據。

    // 方法二:將DataContext強轉為ItemSource對應的實體類類型
    GoodsJsonData data = (GoodsJsonData)btn.DataContext;
    // do what you want !

坑點:

  • 如果這個DataList列表的內容需要同步刷新,則類型**必須是**ObservableCollection。否則就算控件與數據綁定成功,控件只在初始化時能夠正確顯示數據,之后數據發生改變時,控件不會自動刷新。
  • WPF可以傳遞控件自身綁定的ItemSource數據,通過ItemSource攜帶的DataContext內容,來代替CommandParameter多傳參的蛋疼問題。

其他建議:

  • 想要在一個控件上傳遞多個參數,可以傳遞控件自身,用控件的Tag和Uid屬性綁定上數據。

 

今天在StackOverflow看到一個關於解決Command和CommandParameter的工具:
http://xcommand.codeplex.com/
以后可能會用到,先Mark。之后抽空看看。


 

方法二:多路綁定MultiBinding結合轉換器Converter的使用

該方法是網上搜到的主流方式。

 


 

方法三:其他Trick

思路:用其他控件的屬性來記錄數據。傳參時傳遞按鈕控件自身,再通過按鈕控件的視覺樹布局找到找到綁定了其他數據的控件。

XAML:

<Grid>
    <Button Content="測試按鈕"
        Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=DataContext.YourCommand}"
        CommandParameter="{Binding RelativeSource={x:Static RelativeSource.Self}}">
    </Button>
    <!-- 用於給點擊按鈕時,傳遞多個參數 -->
    <Grid x:Name="studentIdGrid" Tag="{Binding studentId}"/>
    <Grid x:Name="studentNameGrid" Tag="{Binding studentName}"/>
    <Grid x:Name="studentAgeGrid" Tag="{Binding studentAge}"/>
</Grid>

Controller:

// 按鈕點擊觸發的事件
private void YourCommand(object p)
{
    Button btn = (Button)p; // 傳參傳遞的是控件自身
    DependencyObject parent = VisualTreeHelper.GetParent(btn);
    List<Grid> list = this.FindVisualChildren<Grid>(parent);
    string studentId = list[0].Tag.ToString();
    string studentName = (int)(list[1].Tag);
    int studentAge = list[2].Tag.ToString();
    
    // do something...
}


// 從視覺樹找到目標控件的所有子控件
private List<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
    List<T> list = new List<T>();
    if (depObj != null)
    {
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
        {
            DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
            if (child != null && child is T)
            {
                list.Add((T)child);
            }

            List<T> childItems = FindVisualChildren<T>(child); // 遞歸
            if (childItems != null && childItems.Count() > 0)
            {
                foreach (var item in childItems)
                {
                    list.Add(item);
                }
            }
        }
    }
    return list;
}

小結:

  • 按鈕的CommandParameter綁定了自身,將自身作為點擊后觸發的回調函數的參數傳入。再用該按鈕控件去找到其他控件或UI元素。
  • 使用了按鈕的兄弟節點Grid的Tag屬性來綁定目標數據,選擇用Grid是因為我們只想用它來傳參,而不需要看到它。因此用其他UI元素的Tag屬性來傳參,再設置Visibility="Collapse"也是可行的。
  • 同樣選擇用兄弟節點也不是必須的。也可以是父節點或子節點,只要能通過視覺樹VisualTreeHelper找到就行。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM