Xaml WinRT Custom User Control with Bindable Collection - c#

I am trying to make a custom user control that I can reuse across all of my views. My BaseViewModel has a property called ViewAlerts that is intended to be used to show alerts consistently across the application (such as successful updates, failed requests, etc.). I was able to get to the point where my custom control is buildable and has a property for binding, but the collection of alerts is never show. I am statically defining some alerts in my base view model for testing purposes, and am not able to get the alerts to show (seems like a problem with an INotifyPropertyChanged but my bindable property inherits from ObservableCollection<> so it should be automatically handling that I think).
Here is my custom control so far:
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../../Resources/MMJCeoResources.xaml" />
<ResourceDictionary>
<Style TargetType="TextBlock" x:Key="AlertTextStyle">
<Setter Property="FontSize" Value="15"></Setter>
<Setter Property="Margin" Value="10" />
<Setter Property="Padding" Value="10" />
</Style>
<DataTemplate x:Key="DangerAlert">
<Grid Background="LightPink">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="10*"/>
</Grid.RowDefinitions>
<Border Width="10"
Height="10"
BorderBrush="DarkRed"
HorizontalAlignment="Right"
Margin="5"
Grid.Row="0">
<TextBlock Text="X" />
</Border>
<TextBlock Grid.Row="1" Text="{Binding Message}" Style="{StaticResource AlertTextStyle}" Foreground="DarkRed"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="SuccessAlert">
<Grid Background="LightGreen">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="10*"/>
</Grid.RowDefinitions>
<Border Width="10"
Height="10"
BorderBrush="DarkGreen"
HorizontalAlignment="Right"
Margin="5"
Grid.Row="0">
<TextBlock Text="X" />
</Border>
<TextBlock Grid.Row="1" Text="{Binding Message}" Style="{StaticResource AlertTextStyle}" Foreground="DarkGreen"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="InfoAlert">
<Grid Background="LightGreen">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="10*"/>
</Grid.RowDefinitions>
<Border Width="10"
Height="10"
BorderBrush="DarkGreen"
HorizontalAlignment="Right"
Margin="5"
Grid.Row="0">
<TextBlock Text="X" />
</Border>
<TextBlock Grid.Row="1" Text="{Binding Message}" Style="{StaticResource AlertTextStyle}" Foreground="DarkGreen"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="WarningAlert">
<Grid Background="LightSalmon">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="10*"/>
</Grid.RowDefinitions>
<Border Width="10"
Height="10"
BorderBrush="DarkOrange"
HorizontalAlignment="Right"
Margin="5"
Grid.Row="0">
<TextBlock Text="X" />
</Border>
<TextBlock Grid.Row="1" Text="{Binding Message}" Style="{StaticResource AlertTextStyle}" Foreground="DarkOrange"/>
</Grid>
</DataTemplate>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Path=Alerts}"
ItemTemplateSelector="{StaticResource AlertDataTemplateSelector}">
</ItemsControl>
</Grid>
</UserControl>
The Code Behind for the control:
public sealed partial class AlertControl : UserControl
{
public static readonly DependencyProperty AlertsProperty = DependencyProperty.Register(
"Alerts", typeof (ObservableList<Alert>), typeof (AlertControl), new PropertyMetadata(default(ObservableList<Alert>), OnAlertsChanged));
public ObservableList<Alert> Alerts
{
get { return (ObservableList<Alert>) GetValue(AlertsProperty); }
set { SetValue(AlertsProperty, value); }
}
//I was trying to adapt another tutorial to what I was trying to accomplish but only got this far
public static void OnAlertsChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var old = e.OldValue as ObservableList<Alert>;
var me = sender as AlertControl;
if (old != null)
{
old.CollectionChanged -= me.OnAlertCollectionChanged;
}
var n = e.NewValue as ObservableList<Alert>;
if (n != null)
n.CollectionChanged += me.OnAlertCollectionChanged;
}
private void OnAlertCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Reset)
{
Alerts.Clear();
var n = e.NewItems as ObservableList<Alert>;
Alerts.AddRange(n);
}
}
public AlertControl()
{
this.InitializeComponent();
DataContext = this;
}
}
an example implementation:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch">
<TextBlock Style="{StaticResource PageTitle}" Text="User Information" />
<controls:AlertControl Alerts="{Binding ViewAlerts}" />
in this implementation, the ViewAlerts property has 4 statically defined alerts in it, so I know there are values that should be showing up.

You should give your inner Grid the DataContext to this and not to the Control itself, because then the outer binding will search for your ViewAlerts inside the Control
<Grid x:Name="InnerGrid">
<ItemsControl ItemsSource="{Binding Path=Alerts}"
ItemTemplateSelector="{StaticResource AlertDataTemplateSelector}">
</ItemsControl>
</Grid>
public AlertControl()
{
this.InitializeComponent();
InnerGrid.DataContext = this;
}
After that you can Bind to Alerts, and Alerts will be binded to the ItemsControl inside your InnerGrid

Related

UWP listview doesn't update after updating/adding an element to the MVVM collection list

I am frustrated with the listview ItemSource and MVVM binding. I am binding the listview with a data model. After loading the listview, I add another item to the listview, the update is not reflected in listview, but it shows that the number of items in the list collection is increased by one. If I add the add in the list collection in the constructor, it is shown in the listview.
XAML
<Page
x:Class="App20.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App20"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.DataContext>
<local:VictimsController></local:VictimsController>
</Page.DataContext>
<Grid Padding="10">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<TextBlock Text="List of Victims/Trapped" HorizontalAlignment="Center" Style="{StaticResource HeaderTextBlockStyle}"/>
</Grid>
<Grid Grid.Row="1">
<ListView x:Name="ls" ItemsSource="{Binding VictimList, Mode=TwoWay}">
<ListView.HeaderTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock FontWeight="Bold" HorizontalAlignment="Center" Grid.Column="0" Text="GPSLatitute" />
<TextBlock FontWeight="Bold" HorizontalAlignment="Center" Grid.Column="1" Text="GPSLongtitude" />
<TextBlock FontWeight="Bold" HorizontalAlignment="Center" Grid.Column="2" Text="Date" />
</Grid>
</DataTemplate>
</ListView.HeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock HorizontalAlignment="Center" Grid.Column="0" Text="{Binding GPSLatitute}" />
<TextBlock HorizontalAlignment="Center" Grid.Column="1" Text="{Binding GPSLongtitude}" />
<TextBlock HorizontalAlignment="Center" Grid.Column="2" Text="{Binding Date}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Grid>
<Button Content="Publish" VerticalAlignment="Top"
Height="76" Width="114" Click="Publish_Click" />
</Grid>
Controller
public class VictimsController : INotifyPropertyChanged
{
List<VictimProfile> _victims = new List<VictimProfile>();
public VictimsController()
{
//Victims.Add(new VictimProfile() { GPSLatitute = 123, GPSLongtitude = 2333 });
}
public List<VictimProfile> VictimList
{
get
{
return _victims;
}
set
{
_victims = value;
OnPropertyChanged("VictimList");
}
}
public void addVictim(VictimProfile profile)
{
_victims.Add(profile);
OnPropertyChanged("VictimList");
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Adding one item to the listview model or collection
async void client_MqttMsgPublishReceived(object sender, MqttMsgPublishEventArgs e)
{
string ReceivedMessage = Encoding.UTF8.GetString(e.Message);
//convert the message to json format
var payLoad = JsonConvert.DeserializeObject<VictimProfile>(ReceivedMessage);
// we need this construction because the receiving code in the library and the UI with textbox run on different threads
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {
txt.Text += ReceivedMessage;
VictimsController model = this.DataContext as VictimsController;
model.addVictim(payLoad); //add item to the list collection
Debug.WriteLine("count: " + ls.Items.Count); //this shows that listview has one more item added to it, but nothing is shown in the listview
//ls.Items.Add(payLoad);
});
}
The point is the list collection has the new item and the ls (listview) also has the item, but it is not shown in the listview.
Finally I solved it.
Changing the list to observable collection has solved the problem.
ObservableCollection<VictimProfile> _victims = new ObservableCollection<VictimProfile>();

Databinding a user control within an itemscontrol

I have a UserControl that has a ViewModel containing an instance of my own custom class, call it Person. This ViewModel is set as the DataContext of the control. I am wanting to use this control in my main window which has, in its ViewModel a List of type Person called People. I am calling the control in my xaml like this:
<ItemsControl ItemsSource="{Binding People}">
<ItemsControl.ItemsTemplate>
<DataTemplate>
<userControls:PersonDetails />
</DataTemplate>
</ItemsControl.ItemsTemplate>
</ItemsControl>
Properties within the control are bound in this manner.
<TextBlock Text="{Binding Person.Name}" />
When I run I am getting the correct number of Controls for the amount of People in the list but no details are populated. I know I am doing something simple wrong but can't find it. Please help.
EDIT:
My UserControl xaml looks like this:
<UserControl x:Class="AddressBook.UserControls.PersonDetails"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:AddressBook.UserControls"
xmlns:vm="clr-namespace:AddressBook.ViewModels"
xmlns:enums="clr-namespace:AddressBook.Models"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="1000">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="40" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<TextBlock Style="{StaticResource PersonDetailHeader}" Text="Person Name:" />
<TextBlock Style="{StaticResource PersonDetailValue}" Grid.Column="1" Text="{Binding Person.Name,
UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Style="{StaticResource PersonDetailHeader}" VerticalAlignment="Center" Grid.Row="1" Text="Street Address:" />
<TextBlock Style="{StaticResource PersonDetailValue}" Grid.Row="1" Grid.Column="1"
Text="{Binding Person.StreetAddress, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Style="{StaticResource PersonDetailHeader}" Grid.Row="2" Text="Town:" />
<TextBlock Style="{StaticResource PeronDetailValue}" Grid.Row="2" Grid.Column="1"
Text="{Binding Person.Town, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Style="{StaticResource PersonDetailHeader}" Grid.Row="3" Text="County:" />
<TextBlock Style="{StaticResource PersonDetailValue}" Grid.Row="3" Grid.Column="1"
Text="{Binding Person.County, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Style="{StaticResource PersonDetailHeader}" Grid.Row="4" Text="Postcode:" />
<TextBlock Style="{StaticResource PersonDetailValue}" Grid.Row="4" Grid.Column="1"
Text="{Binding Person.Postcode, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Style="{StaticResource PersonDetailHeader}" Grid.Row="5" Text="Phone:" />
<TextBlock Style="{StaticResource PersonDetailValue}" Grid.Row="5" Grid.Column="1"
Text="{Binding Person.Phone, UpdateSourceTrigger=PropertyChanged}" />
<StackPanel Grid.Row="7" Grid.ColumnSpan="2" HorizontalAlignment="Center" Orientation="Horizontal">
<Button Style="{StaticResource ButtonStyle}" Content="Click" Command="{Binding ButtonClickCommand}" />
</StackPanel>
</Grid>
UserControl ViewModel:
public class PersonDetailsViewModel
{
public Person Person { get; set; }
public Command ButtonClickCommand { get; }
public PersonDetailsViewModel()
{
ButtonClickCommand = new Command(ButtonClick);
}
private void ButtonClick(object obj)
{
throw new NotImplementedException();
}
UserControl.xaml.cs:
public partial class PersonDetails : UserControl
{
public PersonDetails()
{
InitializeComponent();
}
}
Person.cs
public class Person : BaseModel
{
public string Name
{
get { return name; }
set
{
name = value;
NotifyPropertyChanged();
}
etc....
MainViewModel:
public class MainViewModel : BaseViewModel
{
public ObservableCollection<Person> People { get; }
= new ObservableCollection<Person>();
public MainViewModel()
{
PopulatePeople();
}
}
Now I understand why you were creating the PersonDetailsViewModel in the UserControl XAML, and what the problem was with the command.
The simplest way to fix this is to make MainViewModel.People a collection of PersonDetailsViewModel instead of Person. That's what I'd do.
But if you want to leave MainViewModel.People as it is, while still using PersonDetailsViewModel inside your UserControl, that makes sense to me. You could do this:
.xaml
<UserControl
x:Class="AddressBook.UserControls.PersonDetails"
DataContextChanged="PersonDetails_DataContextChanged"
...etc...
>
<!-- Give the Grid a name... -->
<Grid x:Name="OuterGrid">
.xaml.cs
public PersonDetails()
{
InitializeComponent();
}
private void PersonDetails_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// ...so we can give the Grid a different DataContext
OuterGrid.DataContext = new PersonDetailsViewModel {
Person = (Person)this.DataContext
};
}
Now it will inherit a Person for its DataContext, but internally it will use a PersonDetailsViewModel based on that Person.
Not sure if that's what you are trying to do.
If you only wish to display some carac of every Person present in your People List you could do :
<Grid>
<ItemsControl ItemsSource="{Binding People}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,0,0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" />
<TextBlock Grid.Column="1" Text="{Binding Age}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
That will bind you Text on the Name property of each element of your list, so on the name of each Person of your list.
As your ItemsSource is already bound to People, the Binding of your Items is already on each Person of your List, so you should call your properties directly as {Binding Name} and not {Binding Person.Name}

Windows UWP - How to programmatically scroll ListView in ContentTemplate

I have a list of chats on the left and messages for a given chat on the right.
I want to have the MessageList to scroll to the bottom whenever it appears or gets its data updated. How can I do this?
My code is based off of Microsoft's Master/Detail view example:
https://github.com/Microsoft/Windows-universal-samples/blob/master/Samples/XamlMasterDetail/cs/MasterDetailPage.xaml
The xaml page:
<Page
x:Class="MyApp.Pages.ChatsPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyApp.Pages"
xmlns:data="using:MyApp.Model.Profile"
xmlns:vm="using:MyApp.ViewModel"
xmlns:util="using:MyApp.Util"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Transitions>
<TransitionCollection>
<NavigationThemeTransition />
</TransitionCollection>
</Page.Transitions>
<Page.Resources>
<util:BoolToVisibilityConverter x:Key="BoolToVisConverter" />
<!--CollectionViewSource x:Name="Chats"
Source="{x:Bind ViewModel}"/>
<CollectionViewSource x:Name="Chat"
Source="{Binding ChatViewModel, Source={StaticResource Chats}}"/>
<CollectionViewSource x:Name="Messages"
Source="{Binding MessageViewModel, Source={StaticResource Chat}}"/-->
<DataTemplate x:Key="MasterListViewItemTemplate" >
<Grid Margin="0,11,0,13" BorderBrush="Gray" BorderThickness="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding ChatName}" Style="{ThemeResource ChatListTitleStyle}" />
<TextBlock
Text="{Binding LastMessage}"
Grid.Row="1"
MaxLines="1"
Style="{ThemeResource ChatListTextStyle}" />
<TextBlock
Text="{Binding LastSender}"
Grid.Column="1"
Margin="12,1,0,0"
Style="{ThemeResource ChatListLastSenderStyle}" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="DetailContentTemplate">
<ListView x:Name="MessageList" ItemsSource="{Binding Messages}" ScrollViewer.VerticalScrollMode="Auto">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel BorderBrush="Black" BorderThickness="1" Padding="1">
<TextBlock Text="{Binding Message}" Style="{StaticResource NewsfeedTextStyle}"/>
<Image Visibility="{Binding Path=IsPhoto, Converter={StaticResource BoolToVisConverter} }" Source="{Binding Photo}" />
<Image Visibility="{Binding Path=IsReaction, Converter={StaticResource BoolToVisConverter} }" Width="200" Height="200" Source="{Binding Reaction}" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Sender}" Style="{StaticResource NewsfeedTimestampStyle}" Margin="1"/>
<TextBlock Text="{Binding SentTime}" Style="{StaticResource NewsfeedTimestampStyle}" Margin="1"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</Page.Resources>
<Grid x:Name="LayoutRoot" Loaded="LayoutRoot_Loaded">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="AdaptiveStates" CurrentStateChanged="AdaptiveStates_CurrentStateChanged">
<VisualState x:Name="DefaultState">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="720" />
</VisualState.StateTriggers>
</VisualState>
<VisualState x:Name="NarrowState">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="MasterColumn.Width" Value="*" />
<Setter Target="DetailColumn.Width" Value="0" />
<Setter Target="MasterListView.SelectionMode" Value="None" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="MasterColumn" Width="320" />
<ColumnDefinition x:Name="DetailColumn" Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Text="Chats"
Margin="12,8,8,8"
Style="{ThemeResource TitleTextBlockStyle}" />
<ListView
x:Name="MasterListView"
Grid.Row="1"
ItemContainerTransitions="{x:Null}"
ItemTemplate="{StaticResource MasterListViewItemTemplate}"
Background="{StaticResource ApplicationPageBackgroundThemeBrush}"
IsItemClickEnabled="True"
ItemClick="MasterListView_ItemClick">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
<ContentPresenter
x:Name="DetailContentPresenter"
Grid.Column="1"
Grid.RowSpan="2"
BorderThickness="1,0,0,0"
Padding="24,0"
BorderBrush="{ThemeResource SystemControlForegroundBaseLowBrush}"
Content="{x:Bind MasterListView.SelectedItem, Mode=OneWay}"
ContentTemplate="{StaticResource DetailContentTemplate}">
<ContentPresenter.ContentTransitions>
<!-- Empty by default. See MasterListView_ItemClick -->
<TransitionCollection />
</ContentPresenter.ContentTransitions>
</ContentPresenter>
</Grid>
I think it's the key point that your ListView is inside of the ContentTemplate of ContentPresenter.
Usually we can use ListViewBase.ScrollIntoView(Object) method method to scroll ListView to the specific item, but when the ListView is inside of DataTemplate, it is unexposed. Here is a method, we can use VisualTreeHelper to get this ListView:
public static T FindChildOfType<T>(DependencyObject root) where T : class
{
var queue = new Queue<DependencyObject>();
queue.Enqueue(root);
while (queue.Count > 0)
{
DependencyObject current = queue.Dequeue();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(current); i++)
{
var child = VisualTreeHelper.GetChild(current, i);
var typedChild = child as T;
if (typedChild != null)
{
return typedChild;
}
queue.Enqueue(child);
}
}
return null;
}
My sample is like this:
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="MasterColumn" Width="320" />
<ColumnDefinition x:Name="DetailColumn" Width="*" />
</Grid.ColumnDefinitions>
<ListView x:Name="MasterListView" Grid.Column="0" ItemsSource="{x:Bind ChatList}" SelectionChanged="MasterListView_SelectionChanged">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:ChatEntity">
<TextBlock Text="{x:Bind Member}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ContentPresenter x:Name="DetailContentPresenter" Grid.Column="1"
Content="{x:Bind MasterListView.SelectedItem, Mode=OneWay}">
<ContentPresenter.ContentTemplate>
<DataTemplate x:DataType="local:ChatEntity">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="FromMessageDataTemplate">
<StackPanel Orientation="Horizontal" FlowDirection="LeftToRight">
<TextBlock Text="{Binding Member}" Width="30" Foreground="Blue" FontWeight="Bold" />
<TextBlock Text=":" Width="10" Foreground="Blue" FontWeight="Bold" />
<TextBlock Text="{Binding Content}" Foreground="Red" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="ToMessageDataTemplate">
<StackPanel Orientation="Horizontal" FlowDirection="RightToLeft">
<TextBlock Text="Me" Width="30" HorizontalAlignment="Right" Foreground="Blue" FontWeight="Bold" />
<TextBlock Text=":" Width="10" HorizontalAlignment="Right" Foreground="Blue" FontWeight="Bold" />
<TextBlock Text="{Binding Content}" HorizontalAlignment="Right" Foreground="Green" />
</StackPanel>
</DataTemplate>
<local:ChatDataTemplateSelector x:Key="ChatDataTemplateSelector"
MessageFromTemplate="{StaticResource FromMessageDataTemplate}"
MessageToTemplate="{StaticResource ToMessageDataTemplate}" />
</Grid.Resources>
<ListView ItemsSource="{x:Bind MessageList}" ItemTemplateSelector="{StaticResource ChatDataTemplateSelector}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Grid>
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
The ChatEntity class and MessageEntity class are like this:
public class ChatEntity
{
public string Member { get; set; }
public ObservableCollection<MessageEntity> MessageList { get; set; }
}
public class MessageEntity
{
public enum MsgType
{
From,
To
}
public string Member { get; set; }
public string Content { get; set; }
public MsgType MessageType { get; set; }
}
and my ChatDataTemplateSelector is like this:
public class ChatDataTemplateSelector : DataTemplateSelector
{
public DataTemplate MessageFromTemplate { get; set; }
public DataTemplate MessageToTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
MessageEntity msg = item as MessageEntity;
if (msg != null)
{
if (msg.MessageType == MessageEntity.MsgType.From)
return MessageFromTemplate;
else
return MessageToTemplate;
}
return null;
}
}
Firstly I loaded the ChatList in the left ListView, and in the SelectionChanged event of the left ListView, I loaded the MessageList, this will ensure the MessageList be updated. It's like manually refreshing ObservableCollection(MessageList) for the right ListView each time you selected a item in the left ListView. But you can also add data to the MessageList in other time, and add data to it whenever there is a new message. The ObservableCollection can automatically get refresh. Here is my code:
private ObservableCollection<MessageEntity> messageList;
private ObservableCollection<ChatEntity> ChatList;
public MainPage()
{
this.InitializeComponent();
messageList = new ObservableCollection<MessageEntity>();
ChatList = new ObservableCollection<ChatEntity>();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ChatList.Add(new ChatEntity { Member = "Tom", MessageList = messageList });
ChatList.Add(new ChatEntity { Member = "Peter" });
ChatList.Add(new ChatEntity { Member = "Clark" });
}
private void MasterListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
messageList.Clear();
for (int i = 0; i < 100; i++)
{
if (i % 2 == 0)
messageList.Add(new MessageEntity { Member = "Tom", Content = "Hello!", MessageType = MessageEntity.MsgType.From });
else
messageList.Add(new MessageEntity { Content = "World!", MessageType = MessageEntity.MsgType.To });
}
var listView = FindChildOfType<ListView>(DetailContentPresenter);
listView.ScrollIntoView(messageList.Last());
}
Data in my sample are all fake. The sample looks a little complex, but it's actually very simple, just use VisualTreeHelper to find the ListView and use its ScrollIntoView method to scroll to the last item.

The name TemplateSelector does not exist in the namespace

I get this error:
Error 1 The name "TemplateSelector" does not exist in the namespace "using:MyApps"
but I don't know why because when I create new project and paste the same code to him everything is working so problem is only in my old project. I also try clean or rebuild project 100 times and manually delete bin folder but still not work.
<Page
x:Class="MyApps.BlankPage1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyApps"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
...
<UserControl>
<UserControl.Resources>
<!-- Template for INCOMNIG messages -->
<DataTemplate x:Key="IncomnigTemplate">
<Grid>
<Grid Margin="27,0,0,0" HorizontalAlignment="Left" Background="#BFE8FF" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="{Binding MessengerMessage}" HorizontalAlignment="Left" Margin="5,5,20,0" VerticalAlignment="Top" Foreground="black"></TextBlock>
<TextBlock Grid.Column="0" Grid.Row="1" Text="{Binding MessengerTime}" HorizontalAlignment="Left" Margin="5,0,0,5" VerticalAlignment="Bottom" FontSize="9" Foreground="#908C8C"></TextBlock>
</Grid>
</Grid>
</DataTemplate>
<!-- Template for OUTGOING messages -->
<DataTemplate x:Key="OutgoinTemplate">
<Grid>
<Grid Margin="27,0,0,0" HorizontalAlignment="Right" Background="Gray" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="{Binding MessengerMessage}" HorizontalAlignment="Left" Margin="5,5,20,0" VerticalAlignment="Top" Foreground="black"></TextBlock>
<TextBlock Grid.Column="0" Grid.Row="1" Text="{Binding MessengerTime}" HorizontalAlignment="Left" Margin="5,0,0,5" VerticalAlignment="Bottom" FontSize="9" Foreground="#908C8C"></TextBlock>
</Grid>
</Grid>
</DataTemplate>
<!-- datatemplate selector -->
<local:TemplateSelector x:Key="MessageTemplateSelector"
EmptyTemplate="{x:Null}"
IncomingMessageTemplate="{StaticResource IncomnigTemplate}"
OutgoingMessageCaptureTemplate="{StaticResource OutgoinTemplate}"/>
</UserControl.Resources>
<ListBox ItemTemplateSelector="{StaticResource MessageTemplateSelector}" x:Name="lbChoosenMessagesUsers" Grid.Column="3" FontSize="13" ItemsSource="{Binding MyDatasCurentUser}" Margin="0,0,50,0">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsEnabled" Value="False"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</UserControl>
Class TeplateSelector inside project:
public class TemplateSelector : DataTemplateSelector
{
public DataTemplate IncomingMessageTemplate { get; set; }
public DataTemplate OutgoingMessageCaptureTemplate { get; set; }
public DataTemplate EmptyTemplate { get; set; }
public new DataTemplate SelectTemplate(object item, DependencyObject container)
{
var x = item as Message;
if (x != null)
{
return null;
}
return EmptyTemplate;
}
}
I've often got problem like this too. My solution is simple: just close all opened XAML files, and then build the project again. It works for me.

Validating Listbox MVVM

I am using Caliburn.Micro to do convention based binding of the contents of my listbox from a BindableCollection in my ViewModel.
I want to perform Validation on it so that when there is no selection and the user exits the form they get that nice red line around the listbox with the error message that they have to select a value. So far Caliburn has worked great for textbox validation but I can't seem to get it to work on the listbox!
On a side note, is there any way to forcibly run a complete validation from the VM? Currently I am merely doing manual validation by checking the values and changing them so it triggers the Validator which is a bit backwards..
Here is the code for my view:
<UserControl x:Class="AddSessionDialogView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit">
<toolkit:BusyIndicator IsBusy="{Binding IsBusy}" BusyContent="{Binding IsBusyStatusDisplay}">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="auto" />
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.Resources>
<Style TargetType="TextBlock">
<Setter Property="Margin" Value="5,9" />
</Style>
<Style TargetType="TextBox">
<Setter Property="Margin" Value="5" />
</Style>
</Grid.Resources>
<TextBlock Text="Session Name" />
<TextBox x:Name="SessionName" Grid.Column="1" />
<TextBlock Text="Scenario" Grid.Row="1" />
<ListBox x:Name="Scenarios" Grid.Row="1" Grid.Column="1" DisplayMemberPath="Name" Margin="5" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Bottom" Grid.Column="1" Grid.Row="3" Margin="5">
<Button x:Name="Cancel" Height="28" Margin="5" Content="Cancel" Padding="0,2" Width="75" />
<Button x:Name="OK" Height="28" Margin="5" Content="OK" Padding="0,2" Width="100" />
</StackPanel>
</Grid>
</toolkit:BusyIndicator>
Here is the (relevant) code for my viewmodel:
public BindableCollection<Scenario> Scenarios { get; set; }
[Required(ErrorMessage = "You must select a scenario")]
public Scenario SelectedScenario { get; set; }
private string _sessionName;
[Required(ErrorMessage="Session Name is invalid")]
public string SessionName
{
get { return _sessionName; }
set { _sessionName = value; NotifyOfPropertyChange(() => SessionName); }
}
public IEnumerable<IResult> OK()
{
if (SelectedScenario == null)
{
// This forces validation check, but doesnt show validation error on the view!
SelectedScenario = new Scenario();
SelectedScenario = null;
}
if (SessionName == null)
SessionName = "";
if (Validator.TryValidateObject(this, new ValidationContext(this), new List<ValidationResult>()))
{
IsBusy = true;
IsBusyStatusDisplay = "Creating Session...";
yield return new CreateSessionResult(SelectedScenario.Id, SessionName);
IsBusy = false;
yield return Close();
}
}

Categories

Resources