问题
Apologies ahead of time if this was asked before. The closest thing I found was (How to use vertical Scroll view to show binding data), which looks promising, though I am not sure if it will work because my understanding is that a template, as the name and definition would imply, has to follow a specific pattern and thus can't be dynamic.
My issue: I am trying to rewrite some of our code to more closely follow MVVM patterns, this of course means making the view as dumb as possible. In my view (xaml.cs) I have code that does the following:
foreach(var tuple in itemsToAdd)
{
var formEntry = formEntries.First(x => x.FormEntryId == tuple.Item1);
var controlType = formatting.GetControlType(formEntry.FormAnswerName,
formEntry.CustomEntryIdentifier);
if (formEntry.Type == 0) {
switch (controlType) {
case CustomControl.Duration:
CustomFields.Children.Add(new DurationView(formEntry.FormEntryId, viewModel));
break;
case CustomControl.TextField:
case CustomControl.InputField:
CustomFields.Children.Add(new InputFieldView(formEntry.FormEntryId, viewModel));
.....//continues like this for different controls.
The list itemsToAdd
is defined in my view model, its job is to figure out which elements from a user generated template need to be displayed. This is of course fine, since per my understanding of MVVM, the view model is what needs to control the behaviors (in this case, what to display) and the UI simply handles displaying the fields.
I was wondering though if it could be possible to also let the view model handle, in essence, setting the item source of the scroll view as we do when we create an item source for a list view (say like a list of available templates), as since each of the controls is a custom view that we made, I don't see a doable way of achieving this as since it can't (from my understanding) follow a template since each control is different, and I don't want to have my view model returning an actual view object as that would violate MVVM principles.
Really in essence is: Am I misunderstanding MVVM in Xamarin, and is it alright for the xaml code behind to add elements like what I show in the code snippet.
Apologies if it is unclear what I am asking for!
回答1:
I am not sure I understand the real problem, are you trying to show a different UI for each itemsToAdd
element according to the Type
property?
If it is the problem, you can bind the entire collection to the ListView
control (or whatever you are using) and use a DataTemplateSelector
to choose the UI you want. Reference
View You can refer to the previous link. It would be like:
<ListView ItemsSource="{Binding FormEntries}" DataTemplateSelector="{StaticResource dataTemplateSelectorName}"/>
Ensure your page data context is set to the view model
ViewModel Assuming your object type is named FormEntry
private ObservableCollection<FormEntry> _formEntries;
public ObservableCollection<FormEntry> FormEntries
{
get => _formEntries;
set
{
_formEntries = value;
// Call the 'RaisePropertyChanged' of the framework you are using
}
}
...
FormEntries = new ObservableCollection<FormEntry>(itemsToAdd);
...
DataTemplateSelector
Refer to the link to build a class that extends DataTemplateSelector
and override the OnSelectTemplate
method to determine the business logic to choose the UI template you want
回答2:
So I was able to figure out how to do it, many thanks to Krusty for pointing me towards the right sources.
I had to create my DataTemplateSelector,
public class MyTemplate: DataTemplateSelector
{
public DataTemplate InputFieldTemplate { get; set; }
public DataTemplate EmptyTemplate { get; set; }
public DataTemplate DurationTemplate { get; set; }
public DataTemplate SignatureDetailTemplate {get; set;}
public DataTemplate SignaturePhoneTemplate { get; set; }
public DataTemplate DateTemplate { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
try
{
ControlsViewModel viewModel = item as ControlsViewModel;
if (viewModel == null)
{
return EmptyTemplate;
}
if (viewModel.ControlType == Enums.CustomControl.InputField || viewModel.ControlType == Enums.CustomControl.TextField)
return InputFieldTemplate;
if (viewModel.ControlType == Enums.CustomControl.Duration)
{
return DurationTemplate;
}
if (viewModel.ControlType == Enums.CustomControl.Signature)
{
if(string.IsNullOrEmpty(viewModel.Disclaimer))
{
return SignatureDetailTemplate;
}
return SignaturePhoneTemplate;
}
return EmptyTemplate;
}
catch
{
throw;
}
}
}
Then I create the corresponding Dictionary for it (In this case, ResourceDictionary.xaml)
<DataTemplate x:Key="EmptyTemplate">
<ViewCell>
<BoxView></BoxView>
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="InputFieldTemplate">
<ViewCell>
<StackLayout Orientation="Vertical"
Padding="0, 20, 0, 0"
HorizontalOptions="FillAndExpand"
VerticalOptions="StartAndExpand">
<Label Text="{Binding Label}"
FontAttributes="Bold"
TextColor="Black"
FontSize="14"
VerticalOptions="Start"
HorizontalOptions="FillAndExpand"/>
<StackLayout
HorizontalOptions="FillAndExpand"
VerticalOptions="Start"
Spacing="0"
Orientation="Vertical">
<Editor
Text="{Binding Answer}"
TextColor="Black"
AutoSize="TextChanges"
IsTextPredictionEnabled="False"
IsSpellCheckEnabled="False"
BackgroundColor="White"
FontSize="14"
VerticalOptions="Start"
HorizontalOptions="FillAndExpand"/>
<BoxView HeightRequest="1"
BackgroundColor="{Binding FieldColor}"
Margin="0"
HorizontalOptions="FillAndExpand"
VerticalOptions="Start"/>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
Finally, in the view that I want to use it, I add it as follows:
<ListView ItemsSource="{Binding NoteObjects}" ItemTemplate="{StaticResource DataTemplateSelector}"/>
来源:https://stackoverflow.com/questions/60209412/mvvm-add-elements-dynamically-to-scrollview