2017-07-03 77 views
1

我有一個可觀察的集合,我在Xamarin表單ListView中顯示。我已經定義了一個細節和摘要模板,用於查看每個列表項目。我希望能夠根據每個項目中的布爾屬性在摘要和詳細模板之間動態更改。如何擁有一個動態的DataTemplateSelector

這是項目。

public class MyItem : INotifyPropertyChanged 
{ 
    bool _switch = false; 
    public bool Switch 
    { 
     get 
     { 
      return _switch; 
     } 
     set 
     { 
      if (_switch != value) 
      { 
       _switch = value; 
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Switch")); 
      } 
     } 

    } 
    public int Addend1 { get; set; } 
    public int Addend2 { get; set; } 
    public int Result 
    { 
     get 
     { 
      return Addend1 + Addend2; 
     } 
    } 
    public string Summary 
    { 
     get 
     { 
      return Addend1 + " + " + Addend2 + " = " + Result; 
     } 
    } 
    public event PropertyChangedEventHandler PropertyChanged; 
} 

這裏是可觀察的集合。請注意,只要開關值發生變化,我將刪除該項目並重新插入。這樣做的原因是強制ListView重新選擇DataTemplate。

public class MyItems : ObservableCollection<MyItem> 
{ 
    protected override void InsertItem(int index, MyItem item) 
    { 
     item.PropertyChanged += MyItems_PropertyChanged; 
     base.InsertItem(index, item); 
    } 
    protected override void RemoveItem(int index) 
    { 
     this[index].PropertyChanged -= MyItems_PropertyChanged; 
     base.RemoveItem(index); 
    } 
    private void MyItems_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) 
    { 
     int index = IndexOf(sender as MyItem); 
     if(index >= 0) 
     { 
      RemoveAt(index); 
      Insert(index, sender as MyItem); 
     } 
    } 
} 

這裏是我的數據模板選擇...

public class MyItemTemplateSelector : DataTemplateSelector 
{ 
    DataTemplate Detail { get; set; } 
    DataTemplate Summary { get; set; } 

    protected override DataTemplate OnSelectTemplate(object item, BindableObject container) 
    { 
     if(item is MyItem) 
     { 
      return (item as MyItem).Switch ? Detail : Summary; 
     } 
     return null; 
    } 
} 

這裏是我的資源定義...

 <DataTemplate x:Key="MyDetail"> 
      <ViewCell> 
       <StackLayout Orientation="Horizontal"> 
        <Switch IsToggled="{Binding Switch}"/> 
        <Entry Text="{Binding Addend1}"/> 
        <Entry Text="{Binding Addend2}"/> 
        <Label Text="{Binding Result}"/> 
       </StackLayout> 
      </ViewCell> 
     </DataTemplate> 
     <DataTemplate x:Key="MySummary"> 
      <ViewCell> 
       <StackLayout Orientation="Horizontal"> 
        <Switch IsToggled="{Binding Switch}"/> 
        <Label Text="{Binding Summary}" VerticalOptions="Center"/> 
       </StackLayout> 
      </ViewCell> 
     </DataTemplate> 
     <local:MyItemTemplateSelector x:Key="MySelector" Detail="{StaticResource MyDetail}" Summary="{StaticResource MySummary}"/> 

下面是我收集的初始化...

 MyItems = new MyItems(); 
     MyItems.Add(new MyItem() { Switch = true, Addend1 = 1, Addend2 = 2 }); 
     MyItems.Add(new MyItem() { Switch = false, Addend1 = 1, Addend2 = 2 }); 
     MyItems.Add(new MyItem() { Switch = true, Addend1 = 2, Addend2 = 3 }); 
     MyItems.Add(new MyItem() { Switch = false, Addend1 = 2, Addend2 = 3 }); 

這就是它看起來的樣子像...

enter image description here

權。所以一切正常。如果切換開關,則該項目的視圖從摘要變爲細節。問題是,這不可能是這樣做的正確方法!這是一個完整的方法來刪除一個列表項並將其放回到同一個地方,以便讓數據模板重新選擇。但我無法想出另一種方式。在WPF中,我在項目容器樣式中使用了一個數據觸發器來根據開關值設置內容模板,但似乎沒有辦法在Xamarin中做同樣的事情。

回答

0

實現此目的的方法不是通過切換模板,而是將內容視圖定義爲模板並更改模板內控件的可見性。顯然沒有辦法讓ListView重新評估項目上的項目模板,但沒有移除它並重新添加它。

這裏是我的內容視圖...

<ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
     xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
     xmlns:local="clr-namespace:XamarinFormsBench" 
     x:Class="XamarinFormsBench.SummaryDetailView"> 
<ContentView.Content> 
    <StackLayout x:Name="stackLayout" Orientation="Horizontal"> 
     <Switch x:Name="toggle" IsToggled="{Binding Switch}"/> 
     <Entry x:Name="addend1" Text="{Binding Addend1}"/> 
     <Entry x:Name="addend2" Text="{Binding Addend2}"/> 
     <Label x:Name="result" Text="{Binding Result}"/> 
     <Label x:Name="summary" Text="{Binding Summary}" VerticalOptions="Center"/> 
    </StackLayout> 
</ContentView.Content> 

這是後面的代碼...

namespace XamarinFormsBench 
{ 
[XamlCompilation(XamlCompilationOptions.Compile)] 
public partial class SummaryDetailView : ContentView 
{ 
    public SummaryDetailView() 
    { 
     InitializeComponent(); 
     toggle.PropertyChanged += Toggle_PropertyChanged; 
     UpdateVisibility(); 
    } 

    private void Toggle_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) 
    { 
     if(e.PropertyName == "IsToggled") 
     { 
      UpdateVisibility(); 
     } 
    } 

    private void UpdateVisibility() 
    { 
     bool isDetail = toggle.IsToggled; 
     addend1.IsVisible = isDetail; 
     addend2.IsVisible = isDetail; 
     result.IsVisible = isDetail; 
     summary.IsVisible = !isDetail; 
     InvalidateLayout(); // this is key! 
    } 
} 
} 

現在主要的網頁包含此...

<ListView ItemsSource="{Binding MyItems}"> 
     <ListView.ItemTemplate> 
      <DataTemplate> 
       <ViewCell> 
        <local:SummaryDetailView/> 
       </ViewCell> 
      </DataTemplate> 
     </ListView.ItemTemplate> 
    </ListView> 

ma的關鍵主要工作是在摘要和細節之間切換時使ContentView的佈局無效。這迫使ListView再次佈局單元格。如果沒有這些,使得隱形的控制消失,使得可見的控制永遠不會顯示。如果ContentView在ListView之外使用,則不需要此操作。這在我看來是ListView中的一個錯誤。如果您可以使ViewCell的佈局無效,您可以讓項目模板切換到工作狀態,但是沒有公共方法(只有受保護的方法)才能執行此操作。

0

幾年前,這對我來說是個棘手的問題。我來到MarkupExtensions和轉換器(IValueConverter)。經過與XAML擴展領域的艱苦鬥爭之後,我發現了一件顯而易見的事情:它不應該這樣做。

對於動態更改(m)組件的任何屬性,您應該使用樣式。屬性的反應(它必須是DependencyProperty來處理組件)的改變很容易通過Stryle.Triggers和Setters進行設置。

<Style x:Key="imbXmlTreeView_itemstyle" TargetType="TreeViewItem"> 
    <Setter Property="Margin" Value="-23,0,0,0" /> 
    <Setter Property="Padding" Value="1" /> 
    <Setter Property="Panel.Margin" Value="0"/> 
    <Style.Triggers> 
     <Trigger Property="IsSelected" Value="True"> 
      <Setter Property="Background" Value="{DynamicResource fade_lightGray}" /> 
      <Setter Property="Foreground" Value="{DynamicResource fade_darkGray}" /> 
     </Trigger> 
     <Trigger Property="IsSelected" Value="False"> 
      <Setter Property="Background" Value="{DynamicResource fade_lightGray}" /> 
      <Setter Property="Foreground" Value="{DynamicResource fade_darkGray}" /> 
     </Trigger> 
    </Style.Triggers> 
</Style> 

考慮上面(剛剛從我的舊項目中複製):DynamicResource可以是你的DataTemplate。

這裏有更準確的例子,你可以使用:

<Style x:Key="executionFlowBorder" TargetType="ContentControl" > 
     <Setter Property="Margin" Value="5" /> 
     <Setter Property="ContentTemplate" > 
      <Setter.Value> 
       <DataTemplate> 
        <StackPanel Orientation="Vertical"> 
        <Border Style="{DynamicResource executionBorder}" DataContext="{Binding}"> 
         <Grid> 
          <Grid.ColumnDefinitions> 
           <ColumnDefinition Width="20" /> 
           <ColumnDefinition Width="1*"/> 
           <ColumnDefinition Width="20" /> 
          </Grid.ColumnDefinitions> 
          <CheckBox IsChecked="{Binding Path=isExecuting}" Content="" Grid.Column="0" VerticalAlignment="Center"/> 
          <Label Content="{Binding Path=displayName, Mode=OneWay}" FontSize="10" Grid.Column="1" FontStretch="Expanded" FontWeight="Black"/> 
          <Image Source="{Binding Path=iconSource, Mode=OneWay}" Width="16" Height="16" Grid.Column="2" HorizontalAlignment="Right" Margin="0,0,5,0"/> 
         </Grid> 
        </Border> 
        <Label Content="{Binding Path=displayComment, Mode=OneWay}" FontSize="9" HorizontalAlignment="Left"/> 
        </StackPanel> 
       </DataTemplate> 
      </Setter.Value> 
     </Setter> 
    </Style> 

當二傳手的值可以是DynamicResource或一個通過你的MarkupExtension交付 - 有些事情就像我在這裏:

using System; 
    using System.Windows; 
    using System.Windows.Markup; 

    #endregion 

    /// <summary> 
    /// Pristup glavnom registru resursa 
    /// </summary> 
    [MarkupExtensionReturnType(typeof (ResourceDictionary))] 
    public class masterResourceExtension : MarkupExtension 
    { 
     public masterResourceExtension() 
     { 
     } 

     public override object ProvideValue(IServiceProvider serviceProvider) 
     { 
      try 
      { 
       return imbXamlResourceManager.current.masterResourceDictionary; 
      } 
      catch 
      { 
       return null; 
      } 
     } 
    } 

的您使用的MarkupExtensions如下例所示: 在XAML代碼中:

<Image Grid.Row="1" Name="image_splash" Source="{imb:imbImageSource ImageName=splash}" Stretch="Fill" /> 

後來補充:只是不要忘了在XAML窗口/控件的頂部添加命名空間/集引用(指向與自定義的MarkupExtension代碼)(在本例中爲imbCore.xaml從相同的解決方案的單獨庫項目):

<Window x:Class="imbAPI.imbDialogs.imbSplash" 
     xmlns:imb="clr-namespace:imbCore.xaml;assembly=imbCore" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     Title="{Binding Path=splashTitle}" Height="666" Width="896" ResizeMode="NoResize" WindowStyle="ToolWindow" Topmost="False" WindowStartupLocation="CenterScreen" 
     xmlns:imbControls="clr-namespace:imbAPI.imbControls"> 
    <Grid> 

也有一點你必須首先編譯它,以讓它在XAML設計師的工作。


使用擴展的C#代碼:

using System; 
    using System.Windows.Markup; 
    using System.Windows.Media; 
    using imbCore.resources; 

    #endregion 

    [MarkupExtensionReturnType(typeof (ImageSource))] 
    public class imbImageSourceExtension : MarkupExtension 
    { 
     public imbImageSourceExtension() 
     { 
     } 

     public imbImageSourceExtension(String imageName) 
     { 
      this.ImageName = imageName; 
     } 

     [ConstructorArgument("imageName")] 
     public String ImageName { get; set; } 

     public override object ProvideValue(IServiceProvider serviceProvider) 
     { 
      try 
      { 

       if (imbCoreApplicationSettings.doDisableIconWorks) return null; 
       return imbIconWorks.getIconSource(ImageName); 

      } 
      catch 
      { 
       return null; 
      } 
     } 
    } 

希望我把你的問題的權利放在首位:)。 現在我不得不睡了:)。祝你好運!

稍後添加:好的,我錯過了你的觀點:)對不起。但是,如果您在我發佈的代碼中找到有用的信息,我會留下回復。再見!

+0

我認爲這一切都在WPF不工作在Xamarin形式。我發現的關於Xamarin Forms的只是WPF最基本的東西。 – AQuirky

相關問題