У меня есть следующий DataTemplate, который я использую для пользовательского макета ListView в своем приложении UWP:
<ResourceDictionary
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:contract7Present = "http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 7)"
xmlns:utils = "using:ClassevivaPCTO.Utils"
x:Class = "ClassevivaPCTO.Controls.DataTemplates.AgendaEventListViewDataTemplate"
xmlns:muxc = "using:Microsoft.UI.Xaml.Controls">
<utils:AgendaEvent x:Key = "AgendaEvent"/>
<DataTemplate x:Key = "AgendaEventListViewDataTemplate"
x:DataType = "utils:AgendaEvent">
<RelativePanel>
<TextBlock x:Name = "eventTitle" TextWrapping = "Wrap"
MaxWidth = "500"
Text = "{x:Bind subjectDesc}"
Style = "{ThemeResource BaseTextBlockStyle}"
Margin = "12,6,0,0" />
<TextBlock RelativePanel.RightOf = "eventTitle" x:Name = "eventType" TextWrapping = "Wrap"
MaxWidth = "500"
Text = "{x:Bind evtCode}"
Style = "{ThemeResource BaseTextBlockStyle}"
Margin = "12,6,0,0" />
</RelativePanel>
</DataTemplate>
</ResourceDictionary>
Значение Text элементов TextBlock привязано к пользовательскому классу типа данных с именем AgendaEvent:
public class AgendaEvent
{
public int evtId { get; set; }
public string evtCode { get; set; }
public DateTime evtDatetimeBegin { get; set; }
public DateTime evtDatetimeEnd { get; set; }
public bool isFullDay { get; set; }
public string notes { get; set; }
public string authorName { get; set; }
public string classDesc { get; set; }
public object subjectId { get; set; }
public object subjectDesc { get; set; }
public object homeworkId { get; set; }
}
И это мой ListView:
<ListView x:Name = "ListViewAgendaEvents"
SelectionMode = "Single"
HorizontalAlignment = "Stretch" VerticalAlignment = "Stretch"
ItemTemplate = "{StaticResource AgendaEventListViewDataTemplate}"
ScrollViewer.VerticalScrollMode = "Disabled"
ScrollViewer.VerticalScrollBarVisibility = "Disabled">
</ListView>
И из массива объектов AgendaEvent, которые я получаю из пользовательской фабрики массивов, я устанавливаю ItemsSource в ListView:
ListViewAgendaEvents.ItemsSource = customArray.AgendaEvents;
Я понял, что для моих нужд eventTitle TextBlock должен иметь свойства authorName и subjectDesc, объединенные вместе в виде строки с пользовательскими декоративными символами. Например: authorName + " - wrote: " + subjectDesc.
Не всегда subjectDesc доступен, поэтому мне нужно динамически настроить текстовый блок eventTitle с различными проверками условий.
Я думал об использовании класса адаптера точно так же, как для списков Android, но с привязкой данных UWP очень запутанно, как выполнить то, что я хочу сделать.
Существует ли простой и понятный способ объединения свойств в настраиваемой строке и выполнения проверок перед отображением свойств для DataTemplate, используемого в ListView в UWP?





Обновление3
UWP не поддерживает множественную привязку данных, он принимает только одно значение. Если вы не хотите изменять привязку Xaml, попробуйте сделать это в DataModel. Создать новую строку сообщения в классе AgendaEvent и выполнить проверки будет самым простым способом.
public class AgendaEvent
{
public int evtId { get; set; }
public string evtCode { get; set; }
public DateTime evtDatetimeBegin { get; set; }
public DateTime evtDatetimeEnd { get; set; }
public bool isFullDay { get; set; }
public string notes { get; set; }
public string authorName { get; set; }
public string classDesc { get; set; }
public object subjectId { get; set; }
public object subjectDesc { get; set; }
public object homeworkId { get; set; }
public string messageString
{
get
{
if (subjectDesc == null)
{
return authorName;
}
return authorName + " - wrote: " + subjectDesc;
}
}
}
Xaml:
<TextBlock x:Name = "eventTitle" TextWrapping = "Wrap"
MaxWidth = "500"
Text = "{x:Bind messageString}"
Style = "{ThemeResource BaseTextBlockStyle}"
Margin = "12,6,0,0" />
Обновление2:
Я придумываю другой способ, который также должен быть в состоянии достичь того, чего вы хотите. Для этого нужны разные шаблоны. Вам просто нужно выбрать другой DataTemplate на основе значений, используя класс DataTemplateSelector
Код программной части:
public class MyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate Normal { get; set; }
public DataTemplate Unusual { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
AgendaEvent agendaEvent = (AgendaEvent)item;
// if you have other reuqirements, just need to add more check and different templates
if (agendaEvent.subjectDesc != null)
{
return Normal;
}
else
{
return Unusual;
}
}
}
Xaml:
<Page.Resources>
<!--tempalte for normal value-->
<DataTemplate x:Key = "NormalItemTemplate" x:DataType = "local:AgendaEvent">
<RelativePanel>
<TextBlock x:Name = "eventTitle" TextWrapping = "Wrap"
MaxWidth = "500"
Margin = "12,6,0,0">
<Run Text = "{x:Bind authorName}"/>
<Run Text = " - wrote: "/>
<Run Text = "{x:Bind subjectDesc}"/>
</TextBlock>
<TextBlock RelativePanel.RightOf = "eventTitle" x:Name = "eventType" TextWrapping = "Wrap"
MaxWidth = "500"
Text = "{x:Bind evtCode}"
Margin = "12,6,0,0" />
</RelativePanel>
</DataTemplate>
<!--template for null value-->
<DataTemplate x:Key = "UnusualItemTemplate" x:DataType = "local:AgendaEvent">
<RelativePanel>
<TextBlock x:Name = "eventTitle" TextWrapping = "Wrap"
MaxWidth = "500"
Margin = "12,6,0,0">
<Run Text = "{x:Bind authorName}"/>
</TextBlock>
<TextBlock RelativePanel.RightOf = "eventTitle" x:Name = "eventType" TextWrapping = "Wrap"
MaxWidth = "500"
Text = "{x:Bind evtCode}"
Margin = "12,6,0,0" />
</RelativePanel>
</DataTemplate>
<local:MyDataTemplateSelector x:Key = "MyDataTemplateSelector"
Normal = "{StaticResource NormalItemTemplate}"
Unusual = "{StaticResource UnusualItemTemplate}"/>
</Page.Resources>
<Grid>
<ListView x:Name = "ListViewAgendaEvents"
SelectionMode = "Single"
HorizontalAlignment = "Stretch"
VerticalAlignment = "Stretch"
ItemTemplateSelector = "{StaticResource MyDataTemplateSelector}"
ScrollViewer.VerticalScrollMode = "Disabled"
ScrollViewer.VerticalScrollBarVisibility = "Disabled">
</ListView>
</Grid>
Обновлять:
Вы можете попробовать создать собственный ValueConverter, чтобы выполнить эту работу. Вы можете проверить значение привязки в событии Converter и настроить выходную строку привязки.
Код программной части:
public class TextBlockValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value == null)
{
return "";
}
else
{
return " - wrote: " + value.ToString();
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
Xaml:
<Page.Resources>
<local:TextBlockValueConverter x:Key = "TextBlockValueConverter"/>
</Page.Resources>
<TextBlock x:Name = "eventTitle" TextWrapping = "Wrap"
MaxWidth = "500"
Margin = "12,6,0,0">
<Run Text = "{x:Bind authorName}"/>
<Run Text = "{x:Bind subjectDesc, Converter = {StaticResource TextBlockValueConverter} }"/>
</TextBlock>
Старый
Существует ли простой и понятный способ объединения свойств в настраиваемой строке для DataTemplate, используемого в ListView в UWP?
Поскольку вы используете TextBlock, решение будет очень простым. Вам просто нужно использовать Run внутри TextBlock. И привяжите нужный текст к другому Run.
Вам просто нужно изменить код следующим образом:
<TextBlock x:Name = "eventTitle" TextWrapping = "Wrap"
MaxWidth = "500"
Margin = "12,6,0,0">
<Run Text = "{x:Bind authorName}"/>
<Run Text = " - wrote: "/>
<Run Text = "{x:Bind subjectDesc}"/>
</TextBlock>
Результат выглядит так:
@Gabboxl Я немного запутался. Обновление не очень понятное. Можете ли вы уточнить окончательное поведение, которое вы хотите получить? Какое свойство нужно проверить и какова логика проверки?
В моем случае я хочу проверить, является ли значение subjectDesc нулевым, прежде чем отображать пользовательское строковое значение в текстовом блоке eventTitle. Если subjectDesc равно нулю, то только authorName я хочу отображать, в противном случае свойства subjectDesc и authorName должны быть объединены в одну строку через строку " - wrote: ", как вы сделали в своем ответе.
@Gabboxl Я обновил свой ответ. Вы можете использовать ValueConverter для достижения этой цели. Пожалуйста, обратитесь к примеру кода в моем коде.
Спасибо за обновление. Действительно ли этот метод единственный способ добиться этого? Мне нужно будет переписать все мои шаблоны для каждого свойства, которое я хочу проверить, и динамически настроить таким образом. Есть ли способ вместо этого имитировать собственный метод класса адаптера Android?
@Gabboxl Судя по вопросу, это единственный способ.
@Gabboxl Я только что придумал другой способ. Вы можете использовать DataTemplateSelector Class, чтобы выбрать другой шаблон для другого значения данных. Пожалуйста, проверьте обновление 2
Большое спасибо за ваши усилия, но я не думаю, что этот способ в лучшем случае соответствует моим потребностям: я планирую иметь много DataTemplates, и, следуя этому пути, мне нужно будет написать много избыточных похожих DataTemplates и Dataselectors, чтобы выполнить то, что я хотят сделать, и это неустойчиво. Я ищу более унифицированный способ управления значениями, отображаемыми в DataTemplates, что-то вроде методологии адаптеров Android.
@Gabboxl UWP не поддерживает множественную привязку данных, он принимает только одно значение. Если вы не хотите изменять привязку Xaml, попробуйте сделать это в DataModel. Создать новую строку сообщения в классе AgendaEvent и выполнить проверки будет самым простым способом. Я обновил свой ответ для простого примера.
Хорошо. Спасибо за обновление, я использовал это последнее решение (но поместил пользовательские свойства в отдельный пользовательский класс) в течение нескольких дней, и я чувствую, что это лучшее решение на данный момент, которое близко к тому, что я хотел сделать. Я не ожидал, что достижение чего-то настолько простого будет настолько неинтуитивным в UWP. Я добавил код, который использовал в отдельном ответе.
Из ответа Роя Ли я использовал следующий метод, который больше всего соответствует моим потребностям и частично напоминает архитектуру адаптеров Android:
Это отдельный класс, который действует как «адаптер» или «модель представления» для элемента списка xaml. В конструкторе он принимает исходный тип объекта со свойствами, которые я хочу обработать:
public class AgendaEventAdapter
{
public AgendaEvent CurrentObject;
public string Title
{
get
{
if (string.IsNullOrEmpty(CurrentObject.subjectDesc))
{
return CurrentObject.authorName;
}
else
{
return CurrentObject.subjectDesc + " (" + CurrentObject.authorName + ")";
}
}
}
public string EventType
{
get
{
return "[" + CurrentObject.evtCode + "]";
}
}
public string Notes => CurrentObject.notes;
public AgendaEventAdapter(AgendaEvent ev)
{
CurrentObject = ev;
}
}
Это DataTemplate, используемый в представлении списка (обратите внимание, что DataType теперь является классом адаптера AgendaEventAdapter):
<ResourceDictionary
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:contract7Present = "http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 7)"
xmlns:converters = "using:ClassevivaPCTO.Converters"
xmlns:utils = "using:ClassevivaPCTO.Utils"
xmlns:adapters = "using:ClassevivaPCTO.Adapters"
x:Class = "ClassevivaPCTO.Controls.DataTemplates.AgendaEventListViewDataTemplate"
xmlns:muxc = "using:Microsoft.UI.Xaml.Controls">
<converters:DateTimeToHourConverter x:Key = "DateTimeToHourConverter" />
<DataTemplate x:Key = "AgendaEventListViewDataTemplate"
x:DataType = "adapters:AgendaEventAdapter">
<RelativePanel>
<TextBlock x:Name = "titoloEvento" TextWrapping = "Wrap"
MaxWidth = "500"
Text = "{x:Bind Title}"
Style = "{ThemeResource BaseTextBlockStyle}"
Margin = "6,6,0,0" />
<TextBlock RelativePanel.RightOf = "titoloEvento" x:Name = "tipoEvento" TextWrapping = "Wrap"
MaxWidth = "500"
Text = "{x:Bind EventType}"
Style = "{ThemeResource BaseTextBlockStyle}"
Margin = "6,6,0,0" />
<TextBlock RelativePanel.Below = "titoloEvento" x:Name = "dataStart"
Text = "{x:Bind CurrentObject.evtDatetimeBegin, Converter = {StaticResource DateTimeToHourConverter}}"
FontStyle = "Italic"
Style = "{ThemeResource BodyTextBlockStyle}"
Margin = "6,0,0,0" />
<TextBlock RelativePanel.RightOf = "dataStart" RelativePanel.Below = "titoloEvento" x:Name = "placeholderData"
Text = "-"
FontStyle = "Italic"
Style = "{ThemeResource BodyTextBlockStyle}"
Margin = "6,0,6,0" />
<TextBlock RelativePanel.RightOf = "placeholderData" RelativePanel.Below = "titoloEvento" x:Name = "dataEnd"
Text = "{x:Bind CurrentObject.evtDatetimeEnd, Converter = {StaticResource DateTimeToHourConverter}}"
FontStyle = "Italic"
Style = "{ThemeResource BodyTextBlockStyle}"
Margin = "0,0,0,0" />
<TextBlock RelativePanel.Below = "dataStart" x:Name = "descriptionEvent"
TextWrapping = "Wrap"
Text = "{x:Bind CurrentObject.notes}"
Style = "{ThemeResource BodyTextBlockStyle}"
Margin = "6,6,0,6" />
</RelativePanel>
</DataTemplate>
</ResourceDictionary>
И это код для создания экземпляра класса адаптера для каждого элемента, который затем передается в свойство ItemsSource списка:
// Wrap each AgendaEvent object in an instance of AgendaEventAdapter and handle null case
var eventAdapters = overviewResult.AgendaEvents
?.Select(evt => new AgendaEventAdapter(evt))
.ToList();
ListViewAgendaDate.ItemsSource = eventAdapters;
Это лучшее решение моей проблемы, так как наличие отдельного класса модели может помочь уменьшить беспорядок в коде, а также может быть полезно в случае использования наблюдаемых объектов, которые связаны с представлениями элемента.
Спасибо, это полезно. Но как применить проверки к свойствам перед их отображением? Я обновил свой вопрос, чтобы запрос был ясен