Force ListView to refresh or Invalidate in UWP - c#

I have a ListView with ItemTemplate. I want to bind one control background in ItemTemplate to 2 properties, one of properties is in ItemsSource and onother one is in my page. since UWP has no multibinding support, I bind it to one property in ItemSource and for another property in my page I want to handle it in my code behind.
<ListView >
<ListView.ItemTemplate>
<DataTemplate>
<Border HorizontalAlignment="Stretch"
x:Name="myborder"
Padding="5,0,5,0"
Background="{Binding myProperty, Converter={StaticResource convertPropertyToBgColor },ConverterParameter=border}">
<StackPanel Padding="0,10,10,10"
Background="{Binding myProperty, Converter={StaticResource convertPropertyToBgColor},ConverterParameter=stack}">
<TextBlock Text="{Binding Text}">
</StackPanel>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
in the convertPropertyToBgColor I get the brush from Resources.
and in code behind when my second desired property is changed I Change My resources. so the brush I have used from resources get changed and because of that I want to call that converter again to refresh Background, I called updateLayout but it doesn't refresh my ListView and it doesn't call myConvereter again. How can I force ListView to recreate or refresh Items that it has made?

Generally you class should implement INotifyPropertyChanged, then once you change the property, usually in its setter you also call OnPropertyChanged event which will update your UI. There are plenty examples of that, here is one.
The other way, may be to call Bindings.Update(), but normally you probably should use the method above.
To make my comments clearer - something like this is possible:
<StackPanel Orientation="Horizontal" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView x:Name="myList" Width="200">
<ListView.ItemTemplate>
<DataTemplate>
<Border HorizontalAlignment="Stretch"
x:Name="myborder"
Padding="5,0,5,0"
Background="{Binding Path=DataContext.MyProperty, ElementName=myList}">
<StackPanel Padding="0,10,10,10">
<TextBlock Text="{Binding}"/>
</StackPanel>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
<x:String>Element 1</x:String>
<x:String>Element 2</x:String>
<x:String>Element 3</x:String>
</ListView>
<Button Content="Change" Click="Button_Click"/>
</StackPanel>
code behind:
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void RaiseProperty(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
private SolidColorBrush myPropety = new SolidColorBrush(Colors.Red);
public SolidColorBrush MyProperty
{
get { return myPropety; }
set { myPropety = value; RaiseProperty(nameof(MyProperty)); }
}
public MainPage()
{
this.InitializeComponent();
this.DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e) => MyProperty = new SolidColorBrush(Colors.Blue);
}

Related

Binding to TextBlock in ListView

I don't know if my understanding of binding is just poor or if I am not seeing the problem, but I hope someone can help me out here. I have a ListView with a template of an image and a TextBlock, I need the TextBlock to be bound to the ItemsSource of the ListView. However when I run this I get nothing shown, I don't even see my image that I have set.
XAML:
<UserControl.Resources>
<FontFamily x:Key="FontFamily">MS Reference Sans Serif</FontFamily>
</UserControl.Resources>
<Grid>
<ListView BorderThickness="0" ItemsSource="{Binding Facies}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="../Images/Shale.png"/>
<TextBlock Text="{Binding FaciesName}" Width="75" Margin="5"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
C#:
public partial class FaciesControl : UserControl
{
public FaciesControl()
{
InitializeComponent();
}
public List<string> Facies {get; set;}
public void Bind(string[] data)
{
Facies = new List<string>();
Facies.AddRange(data);
}
}
First set DataContext like this:
public FaciesControl()
{
InitializeComponent();
string[] str = { "Name1", "Name2", "Name3" };
Bind(str); // Make sure you have called the Bind method
DataContext = Facies;
}
Second change your XAML like this:
<ListView BorderThickness="0" ItemsSource="{Binding}">
....
....
<TextBlock Text="{Binding}" Width="75" Margin="5"/>

Windows Phone - custom control how propagate event to ViewModel

I am creating custom control for menu and I have ListBox in my control. Something like this:
<ListBox x:Name="MenuItemsList"
Grid.Row="1"
ItemsSource="{Binding ProgramList, Mode=OneWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10">
<TextBlock Text="{Binding Title}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now when I want to catch Tap event, get properties from class and keep MVVM model. How can I catch this in MainPage.xaml.cs or in MainViewModel?
I have this code in my MainPage.xaml:
<controls:BottomMenu x:Name="BottomMenu" Canvas.Top="{Binding MenuCanvasTop}"
Width="480" Height="400">
</controls:BottomMenu>
I have prepare this code in my MainViewModel:
public RelayCommand<string> GoToSectionCommand
{
get
{
return goToArticleCommand
?? (goToArticleCommand = new RelayCommand<string>(
NavigateToSection));
}
}
But I don't know how can I call it. What's the best way?
Edit:
I tried to extend listbox:
<ListBox x:Name="MenuItemsList"
Grid.Row="1"
ItemsSource="{Binding ProgramList, Mode=OneWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10">
<Button Content="{Binding Title}"
Command="{Binding ListButtonClickCommand, Source={RelativeSource Self}}"
CommandParameter="{Binding Url}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
With code-behind:
public ICommand ListButtonClickCommand
{
get { return (ICommand)GetValue(ListButtonClickCommandProperty); }
set { SetValue(ListButtonClickCommandProperty, value); }
}
public static readonly DependencyProperty ListButtonClickCommandProperty =
DependencyProperty.Register("ListButtonClickCommand", typeof(ICommand), typeof(BottomMenu), new PropertyMetadata(null));
public BottomMenu()
{
InitializeComponent();
}
Then in MainPage.xaml:
<controls:BottomMenu x:Name="BottomMenu" Canvas.Top="{Binding MenuCanvasTop}"
Width="480" Height="400"
ListButtonClickCommand="{Binding MenuItemButtonCommand}">
</controls:BottomMenu>
And in MainViewModel:
private ICommand menuItemButtonCommand;
public ICommand MenuItemButtonCommand
{
get
{
return menuItemButtonCommand
?? (menuItemButtonCommand = new RelayCommand(
NavigateToArticleSection));
}
}
For now without luck. It's not working. RelayCommand isn't triggered.
Edit2
I guess the problem is with binding command in custom control but I don't know how to fix it.
You just need to bind to the UserControl -- using "RelativeSource Self" will bind to the Button, which is not what you want. You should be able to use an "ElementName" binding to locate the user control:
<UserControl x:Name="UserControlName" ... >
...
<Button Content="{Binding Title}"
Command="{Binding ElementName=UserControlName,Path=ListButtonClickCommand}"
CommandParameter="{Binding Url}"/>
...
</UserControl>

windows store apps How to bind visibility of an appbar item?

I have some problems with binding a visibility property of an appbar button.
I want to bind an appbar button visibility to another element visibility.
If the another element is visible - then the appbar is visible.
So here is my code:
<common:LayoutAwarePage.BottomAppBar>
<AppBar>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Visibility="{Binding ElementName=btnSave, Path=Visibility}"
Click="Edit_Click" />
...(more buttons)
</StackPanel>
</AppBar>
</common:LayoutAwarePage.BottomAppBar>
<Button Grid.Row="7" Grid.Column="0"
x:Name="btnSave"
Content="Save"
Style="{StaticResource EditModeButtonStyle}"
Click="Save_Click" />
I am changing the btnSave visibility in the code behind and no reaction in the appbar button's visibility. I have even tried to do the same binding with just a textblock, and it worked fine. I have also tried to use converter on the appbar (even thought I don't need) and I saw that the debugger was not reading the converter's methods. I saw some more people wrote similar appbar problems, but nothing of the answers is not helping me. Does someone know how can I do it? (I don't want to use code behind to change appbar visibility).
I suspect that appbar elements are not seeing the page's elements and hence element binding is not working. I would recommend you to use independent property which implements INotifyPropertyChanged interface. Bind that property to those elements for which you want to set the visibility.
C#
public sealed partial class BlankPage4 : Page, INotifyPropertyChanged
{
private Visibility _IsHide;
public Visibility IsHide
{
get { return _IsHide; }
set
{
_IsHide = value;
OnPropertyChanged("IsHide");
}
}
public BlankPage4()
{
this.InitializeComponent();
DataContext = this;
}
private void btnHideAll_Click(object sender, RoutedEventArgs e)
{
IsHide = Visibility.Collapsed;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
XAML
<Page.BottomAppBar>
<AppBar IsSticky="True" IsOpen="True">
<StackPanel Orientation="Horizontal">
<Button x:Name="btnHello" Visibility="{Binding IsHide}" Content="Hello" />
<TextBlock Visibility="{Binding IsHide}" Text="Hello" FontSize="20"/>
</StackPanel>
</AppBar>
</Page.BottomAppBar>
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<StackPanel>
<Button x:Name="btnSave" Visibility="{Binding IsHide}" Content="Save" />
<Button Content="Hide All" Click="btnHideAll_Click" />
</StackPanel>
</Grid>

How to set different HorizontalAlignment to ListBoxItems

I posted a question yesterday but I think I failed to explain it correctly.
Let me try again.
So this is my goal:
The red speech bubble represents an incoming message, and the blue bubble an outgoing message. I can describe this more precisely with the following xaml code. Note that the following code is only an explanation of what I expect to get when my actual xaml code (with some DataTemplates) compiles (WPF will populates the data automatically for me, using the DataTemplates). :
<ListBox>
<ListBoxItem HorizontalAlignment="Right">
<Grid Background="Blue">
<TextBlock Text="Help me please!" FontSize="30"/>
</Grid>
</ListBoxItem>
<ListBoxItem HorizontalAlignment="Left">
<Grid Background="Red">
<TextBlock Text="What do you want?" FontSize="30"/>
</Grid>
</ListBoxItem>
<ListBoxItem HorizontalAlignment="Right">
<Grid Background="Blue">
<TextBlock Text="I want a ListBox" FontSize="30"/>
</Grid>
</ListBoxItem>
<ListBoxItem HorizontalAlignment="Left">
<Grid Background="Red">
<TextBlock Text="Then?" FontSize="30"/>
</Grid>
</ListBoxItem>
<ListBoxItem HorizontalAlignment="Right">
<Grid Background="Blue">
<TextBlock Text="But the Grid won't fill" FontSize="30"/>
</Grid>
</ListBoxItem>
</ListBox>
In order to achive this, I wrote:
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxItem>
<Grid Background="{Binding Color}">
<TextBlock Text="{Binding Text}" FontSize="30"/>
</Grid>
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Note that alignment is not specified in the code above, becuase I really don't know how to set different alignement for ListBoxItem separately using templates. So this would result in the situation where all the blue and red grids are all aligned to the left, by default.
My first approach includes a Data Template selector (The template for incoming messages is omitted):
<ListBox>
<ListBox>
<ListBox.ItemTemplate>
<!-- local:MessageBubbleTemplateSelector.OutgoingMessageTemplate -->
<DataTemplate>
<ListBoxItem>
<Grid>
<Grid Background="{Binding Color}" HorizontalAlignment="Right">
<TextBlock Text="{Binding Text}" FontSize="30"/>
</Grid>
</Grid>
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ListBox>
But this did not work. Because the Grid which wraps the speech bubble won't expand automatically, so the alignment of the Grid inside this Grid did not matter (tightly fitted).
And then I went for searching how to expand a Grid inside a StackPanel, and got no luck.
After many hours of googling and trials and errors, I decided to define the template for the ItemsPanelTemplate myself. I have a property in my Message object that can help me tell an incoming message from an outgoing one. But I don't know how to create an ItemsPanelTemplate selector (For the record, Google told me that Style.Trigger is not supported in Windows Phone 8).
So my question is: how to set different HorizontalAlignment for ListBoxItems?
BTW, ItemsPabelTemplate looks like this:
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
Thank you so much for your patience. I am madly desperate here already... So many hours wasted on this...
Note: I do not have Phone SDK so had to make do with normal WPF app. I have not used triggers as you mentioned they do not work.
So I knocked up a simple app that looks like this
Here's the code:
App.xaml.cs
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var mainvm = new MainWindowViewModel();
var window = new MainWindow
{
DataContext = mainvm
};
window.Show();
mainvm.Messages.Add(new OutgoingMessage{ MessageContent = "Help me please!"});
mainvm.Messages.Add(new IncomingMessage { MessageContent = "What do you want" });
mainvm.Messages.Add(new OutgoingMessage { MessageContent = "I want a ListBox" });
mainvm.Messages.Add(new IncomingMessage { MessageContent = "Then?" });
mainvm.Messages.Add(new OutgoingMessage { MessageContent = "But the Grid won't fill" });
}
}
MainWindow.xaml
<Window x:Class="ChatUI.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ChatUI"
Title="MainWindow" Height="350" Width="200">
<Window.Resources>
<DataTemplate DataType="{x:Type local:IncomingMessage}">
<Grid Margin="0,10">
<Border CornerRadius="8" Background="Red" BorderBrush="Black" BorderThickness="1" />
<TextBlock Text="{Binding MessageContent}" HorizontalAlignment="Left" Margin="5" Foreground="White"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type local:OutgoingMessage}">
<Grid Margin="0,10">
<Border CornerRadius="8" Background="Blue" BorderBrush="Black" BorderThickness="1" />
<TextBlock Text="{Binding MessageContent}" HorizontalAlignment="Right" Margin="5" Foreground="White"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid Background="Black">
<ItemsControl ItemsSource="{Binding Path=Messages}"/>
</Grid>
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
}
MainWindowViewModel:
public class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel()
{
Messages = new ObservableCollection<Message>();
}
public ObservableCollection<Message> Messages { get; protected set; }
}
Message.cs:
public abstract class Message : ViewModelBase
{
private string _messageContent;
public string MessageContent
{
get
{
return this._messageContent;
}
set
{
this._messageContent = value;
this.OnPropertyChanged("MessageContent");
}
}
}
OutgoingMessage.cs
public class OutgoingMessage : Message
{
}
IncomingMessage.cs
public class IncomingMessage : Message
{
}
How this works
I override the application startup so I can create viewmodels to populate my UI. You can see in the App.xaml.cs code I create the Window and show it, and then add the messages. I was going to use a timer but got lazy.
If you look at the MainWindow.xaml, you will notice that I have 2 DataTemplates defined. One of them targets my IncomingMessageViewModel and the other targets the OutogingMessageViewModel. The local prefix is an alias for my application namespace. I have an ItemsControl that can contain the base type Message class, just so that I can have both Incoming and Outgoing messages in the same collection. This is bound to the Messages property on my MainWindowViewModel class. It is important to have incoming and outgoing messages as 2 separate classes as this is the magic that makes this work.
An alternative technique would be to use a property with a style selector bound to the property as one of the other answers suggest, but this would mean that I would have to deal with UI specific logic in my ViewModel (which I don't like to do).
To change the appearance of either Message type, just change the xaml code in the respective DataTemplate.
Hope this helps.
In WPF you would need to add
<ListBox.ItemContainerStyle>
<Style>
<Setter Property="HorizontalAlignment" Value="{Binding WHATEVER}" />
</Style>
</ListBox.ItemContainerStyle>
to your ListBox and set the "WHATEVER" to a property of your items that has the alignment specified... I don't know if that works for Windows Phone but it seems worth a try since you didn't mention the ItemContainerStyle...
Instead of using Grid, use DockPanel with HorizontalAlignment="Stretch" instead.
As for the data alignment, assuming you are using ItemsSource, then there is some workaround.
First, the easy thing to do is to add HorizontalAlignment WPF property to your message class. The message class will determine whether the HorizontalAlignment will be left or right. However this will make dependency more higher with the UI.
The code will be like this:
<TextBlock Text="{Binding Text}" HorizontalAlignment="{Binding MessageHAlign}" />
Second, the better (or clean) way to do is to do HorizontalAlignment binding with converter (IValueConverter). It is harder and you must define your own converter, but your code will be tidier. Then your message has an enum of Income or Outcome message, named MessageType. Then in your converter define it like:
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if(parameter is MessageType){
if(((MessageType)parameter) == MessageType.Income){
return HorizontalAlignment.Left;
}
else{
return HorizontalAlignment.Right;
}
}
}
The code above is not tested, so please consider error.
For the implementation of Converter, please search it in some places. I still cannot generate Converter binding without help source :)
Try this and set Property according to incoming as Left and Outgoing as Right
<DataTemplate>
<ListBoxItem>
<Grid>
<Grid Background="{Binding Color}" HorizontalAlignment="{Binding Property}">
<TextBlock Text="{Binding Text}" FontSize="30"/>
</Grid>
</Grid>
</ListBoxItem>
</DataTemplate>
that would be my dirty working example
code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var l = new List<lItem>();
for(int i=0;i<5;i++)
{
l.Add(new lItem(true,"aaa"+i));
l.Add(new lItem(false,"bbb"+i));
}
sads.ItemsSource = l;
}
}
public class lItem
{
public string Text { get; set; }
public Brush Color { get; set; }
public HorizontalAlignment alig { get; set; }
public lItem(bool ss, string str)
{
Text = str;
Color = Brushes.Blue;
alig = HorizontalAlignment.Right;
if (ss)
{
Color = Brushes.Red;
alig = HorizontalAlignment.Left;
}
}
}
Xaml
<ListBox Name="sads" Width="230">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxItem>
<Grid Width="200">
<Label Background="{Binding Color}" VerticalAlignment="Top" HorizontalAlignment="{Binding alig}" >
<TextBlock Text="{Binding Text}" FontSize="30"/>
</Label>
</Grid>
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
i would recommend to use trigger instead of define visual parts in your ViewModel

ItemsControl inside my UserControl is not updating

I'm having trouble with my UserControl with following source code:
[ContentProperty("SetContent")]
public partial class HeaderContainer : UserControl
{
// Header region
public FrameworkElement SetContent
{
get { return (FrameworkElement)GetValue(SetContentProperty); }
set { SetValue(SetContentProperty, value); }
}
public static readonly DependencyProperty SetContentProperty =
DependencyProperty.Register("SetContent", typeof(FrameworkElement),
typeof(HeaderContainer), new PropertyMetadata(null));
public HeaderContainer()
{
InitializeComponent();
DataContext = this;
}
}
This is my XAML
<StackPanel>
<Border>
<TextBlock Text="{Binding Header}" />
</Border>
<ContentPresenter Content="{Binding SetContent}"/>
</StackPanel>
And here is my Problem:
<c:HeaderContainer Header="List">
<ItemsControl ItemsSource="{Binding ObjectList}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding DisplayName}"/>
<TextBlock Text="{Binding SecondLine}" Foreground="Gray" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</c:HeaderContainer>
The Itemscontrol itself is working, also the UserControl, when I add content in XAML. The problem is, when I add Items to ObjectList while it's in the HeaderContainer - nothing happens. Where am I thinking wrong?
It is hard to determine the exact issue here because your code is incomplete. You have a SetContent dependency property, however your example usage doe not use it. I think your approach should be to sub-class ContentControl, adding your Header property to this.
Or ... just use the Silverlight Toolkit HeaderedContentControl which I think does exactly what you are trying to achieve!

Categories