Introduction :
Hi, I meet a weird problem here. My ItemsControl doesn't update the view if I am updating the model (e.g. changing the value of IsSelected).
One of IsSelected purpose however, is, if the value is true, then the Background of the MusicalNotationBox (UserControl) is changed to blue, and if it's false then it's changed back to transparent.
A lot of person asked : Why not using trigger such as Focus for IsSelected? Because it's not just
for "visual" purposes. I have a Command which to change certain
Property (for example Note's Octave) of each MusicalNotation
object in VM's MusicalNotations, which IsSelected==true (support
multiselection), so I think I need IsSelected in the model. But this is not the problem here, so please no answer solely on this matter.
The problem is :
The model property is changed successfully (checked and verified), but it seems that the view isn't.
If I use the singular form of the UserControl, for e.g. <c:MusicalNotationBox DataContext={Binding}/> (ofc along with it's friends a.k.a correct properties in the VM), it synced perfectly. So, I'm not quite sure where the problem lies with the OC.
UPDATE : If I, for example create a MouseBinding that each time I click, then a new MusicalNotation added to the list, then, the view is also updated.
I have read several topic (google and here) on "Observable Collection
doesn't update the ItemsControl`, but still found no satisfying answer.
Here's my code (code may trimmed (...) for clarity sake) :
MODEL
public class MusicalNotation : ... INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
...
private bool _isSelected;
...
public bool IsSelected
{
get { return _isSelected; }
set { _isSelected = value; NotifyPropertyChanged("IsSelected"); }
}
...
public MusicalNotation()
{
...
IsSelected = false;
}
...
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
VIEW MODEL
public class MainWindowModelView : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<MusicalNotation> _musicalNotations;
...
public ObservableCollection<MusicalNotation> MusicalNotations
{
get { return _musicalNotations; }
set { _musicalNotations = value; NotifyPropertyChanged("MusicalNotations"); }
}
public MainWindowModelView()
{
...
MusicalNotations = new ObservableCollection<MusicalNotation>();
//Direct initialization for testing purpose
MusicalNotations.Add(MusicalNotation.GetEmptyNote(new TimeSignature(4, 4)));
MusicalNotations.Add(MusicalNotation.GetEmptyNote(new TimeSignature(4, 4)));
foreach (MusicalNotation item in MusicalNotations)
{
item.IsSelected = true;
}
}
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
VIEW
<Window ...>
<Window.Resources>
</Window.Resources>
<Window.InputBindings>
...
</Window.InputBindings>
<Grid>
...
<ItemsControl Grid.Column="0" Grid.Row="0" ItemsSource="{Binding MusicalNotations, Mode=OneWay}"
HorizontalAlignment="Center" VerticalAlignment="Center">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel IsItemsHost="True" Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<c:MusicalNotationBox/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Thanks.
UPDATE, My MusicalNotationBox XAML
<UserControl x:Class="NumberedMusicScoresUserControl.MusicalNotationBox"
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:NumberedMusicScoresUserControl.MusicalNotationBoxProperties"
mc:Ignorable="d">
<UserControl.Resources>
<local:DotConverter x:Key="DotConverter"/>
<local:NoteConverter x:Key="NoteConverter"/>
<local:AccidentalConverter x:Key="AccidentalConverter"/>
<local:IsSelectedConverter x:Key="IsSelectedConverter"/>
</UserControl.Resources>
<Grid x:Name="grid" Margin="10,5,10,5"
HorizontalAlignment="Center" VerticalAlignment="Center"
Background="{Binding Path=MusicalNotation.IsSelected, Converter={StaticResource IsSelectedConverter}, Mode=OneWay}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="1"
Text="b"
Visibility="{Binding Path=MusicalNotation.Accidental, Converter={StaticResource AccidentalConverter}, ConverterParameter=FL, Mode=OneWay}"
FontSize="15" FontFamily="CourierNew"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Path Grid.Column="1" Grid.Row="1" Stroke="Black" StrokeThickness="1" Stretch="Fill"
Visibility="{Binding Path=MusicalNotation.Accidental, Converter={StaticResource AccidentalConverter}, ConverterParameter=SP, Mode=OneWay}" >
<Path.Data>
<LineGeometry StartPoint="1,0" EndPoint="0,1">
<LineGeometry.Transform>
<RotateTransform CenterX="0" CenterY="0" Angle="30"/>
</LineGeometry.Transform>
</LineGeometry>
</Path.Data>
</Path>
<TextBlock Grid.Column="1" Grid.Row="1"
Text="{Binding Path=MusicalNotation.Note, Converter={StaticResource NoteConverter}, Mode=OneWay}"
FontSize="15" FontFamily="CourierNew"
HorizontalAlignment="Center" VerticalAlignment="Center"
Margin="2.5,0,2.5,0"/>
<ItemsControl Grid.Column="1" Grid.Row="0"
ItemsSource="{Binding Path=MusicalNotation.Octave, Converter={StaticResource DotConverter}, ConverterParameter=TOP, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse HorizontalAlignment="Center" VerticalAlignment="Top"
Margin="{Binding Margin}" Fill="{Binding Fill}"
Width="{Binding Diameter}" Height="{Binding Diameter}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl Grid.Column="1" Grid.Row="2"
ItemsSource="{Binding Path=MusicalNotation.Octave, Converter={StaticResource DotConverter}, ConverterParameter=BOT, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse HorizontalAlignment="Center" VerticalAlignment="Bottom"
Margin="{Binding Margin}" Fill="{Binding Fill}"
Width="{Binding Diameter}" Height="{Binding Diameter}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl Grid.Column="2" Grid.Row="1"
ItemsSource="{Binding Path=MusicalNotation.Dot, Converter={StaticResource DotConverter}, ConverterParameter=RIGHT, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse HorizontalAlignment="Left" VerticalAlignment="Center"
Margin="{Binding Margin}" Fill="{Binding Fill}"
Width="{Binding Diameter}" Height="{Binding Diameter}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
(I didn't include it since, I think, if the "singular" one is correct, then my UserControl is correct as well. Might be wrong though.)
The DataContext for your user control is the MusicalNotation object, so while binding instead of using
Background="{Binding Path=MusicalNotation.IsSelected, Converter={StaticResource ....
just use
Background="{Binding Path=IsSelected, Converter={StaticResource ....
Drop the MusicalNotation prefix from the bindings.
{Binding Path=IsSelected, ...
As you haven't specified any binding for the selected item or for the IsSelected in your xaml, that's why view is not being updated when the property is changed in the VM. For updating the view you will have to provide a binding for SelectedItem property of ItemsControl.
Related
I have something like a regex search pattern and this pattern consists of objects of type Variable or Literal. I need to show those objects one after another in line like this:
Variable is blue, Literal is red. I have defined two DataTemplates to set the colors.
<DataTemplate DataType="{x:Type local:Literal}">
<Border BorderThickness="1" BorderBrush="Red" Padding="5" Margin="0 0 1 0"
Background="Black">
<TextBox Text="{Binding Text}" />
</Border>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Variable}">
<Border BorderThickness="1" BorderBrush="Blue" Padding="5" Margin="0 0 1 0"
Background="Black">
<TextBox Text="{Binding Text}" />
</Border>
</DataTemplate>
My idea was to put a collection of these objects into a ListView and add delete buttons somehow so I can delete objects from the collection, but I am not sure how do that.
<ListView ItemsSource="{Binding RegExList}" Margin="5" Grid.Column="0"
HorizontalAlignment="Left" VerticalAlignment="Center">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ListView>
RegExList is where those objects are.
You could add commands to the view model that contains the RegExList to delete items.
There are different implementations of ICommand. If you do not have a concrete command type, you could take it e.g. from here. The RelayCommand takes a method to execute and a method that returns whether the command can be executed with the given parameter. If you are new to commands, you can have a look at this article.
public class MyViewModel : INotifyPropertyChanged
{
public ICommand DeleteVariable { get; }
public ICommand DeleteLiteral { get; }
// ...your RegExList collection, other properties.
public MyViewModel()
{
DeleteVariable = new RelayCommand<Variable>(ExecuteDeleteVariable, CanExecuteDeleteVariable);
DeleteLiteral = new RelayCommand<Literal>(ExecuteDeleteLiteral, CanExecuteDeleteLiteral);
}
private void CanExecuteDeleteVariable(Variable variable)
{
// Optionally add conditions on when deletion is allowed
return true;
}
private void ExecuteDeleteVariable(Variable variable)
{
RegExList.Remove(variable);
}
private void CanExecuteDeleteLiteral(Literal literal)
{
// Optionally add conditions on when deletion is allowed
return true;
}
private void ExecuteDeleteLiteral(Literal literal)
{
RegExList.Remove(literal);
}
}
Bind a button in the DataTemplate to the corresponding command of the parent data context and bind the current item as CommandParameter.
<DataTemplate DataType="{x:Type local:Literal}">
<Border BorderThickness="1" BorderBrush="Red" Padding="5" Margin="0 0 1 0"
Background="Black">
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Text}" />
<Button Content="X"
Command="{Binding DataContext.DeleteVariable, RelativeSource={RelativeSource AncestorType={x:Type ListView}}}"
CommandParameter="{Binding}"/>
</StackPanel>
</Border>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Variable}">
<Border BorderThickness="1" BorderBrush="Blue" Padding="5" Margin="0 0 1 0"
Background="Black">
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Text}" />
<Button Content="X"
Command="{Binding DataContext.DeleteLiteral, RelativeSource={RelativeSource AncestorType={x:Type ListView}}}"
CommandParameter="{Binding}"/>
</StackPanel>
</Border>
</DataTemplate>
As a note, your RegExList collection should be an ObservableCollection<T>, otherwise removing items from the collection will not be reflected in your ListView.
I want to make a list of ToggleButtons. Each of them is binded to a popup.
I had a problem with that, so I tried to put everything inside a StackPanel.
But, now, when the app is running, it shows an empty space (for the Popup) right after the ToggleButton. What can I do to solve that?
I've just added two images:
The first one is when the page is being uploaded.
The second one is when I scroll down the page.
<ListView x:Name="ListOfRecipes" HorizontalAlignment="Center" VerticalAlignment="Top" ItemsSource="{Binding}" Grid.Row="1" Margin="25,0.333,25,35" ScrollViewer.VerticalScrollMode="Enabled" Grid.RowSpan="5" >
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<ToggleButton x:Name="RecipeButton" Grid.Row="1" BorderBrush="#FF65C365" VerticalAlignment="Center" HorizontalAlignment="Center" Click="Button_Click" Height="150" Width="328" >
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Height="128" Width="328">
<Image Source="{Binding Path=ImageUri}" Height="128" Width="128" Margin="0,6,0,-5.667" />
<StackPanel Orientation="Vertical" HorizontalAlignment="Left" VerticalAlignment="Top" Height="128" Width="192">
<TextBlock Height="25" Width="190" Foreground="#FF6FDC13" Text="{Binding Name}" VerticalAlignment="Top" />
<Image Name="YesOrNoImage" Source="{Binding Path=YesOrNoImage}" Width="102" Height="102" HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
</StackPanel>
</StackPanel>
</ToggleButton>
<Popup IsOpen="{Binding IsChecked, ElementName=RecipeButton, Mode=TwoWay}" Height="514" Width="328" VerticalAlignment="Center" Name="PopupOne" Grid.Row="1" Grid.RowSpan="4" IsLightDismissEnabled="True" IsHoldingEnabled="False" ScrollViewer.VerticalScrollMode="Enabled" >
<Border BorderBrush="#FF65C365" BorderThickness="1" Background="White" Height="514" Width="328">
<StackPanel Orientation="Vertical" ScrollViewer.VerticalScrollMode="Enabled">
<Image Source="{Binding Path=ImageUri}" Height="328" Width="328" />
<TextBlock Foreground="#FF6FDC13" Text="{Binding Name}" HorizontalAlignment="Left" FontSize="28" />
<ScrollViewer VerticalScrollMode="Enabled" >
<TextBlock Foreground="Black" Text="{Binding RecipeText}" HorizontalAlignment="Left" FontSize="18" />
</ScrollViewer>
</StackPanel>
</Border>
</Popup>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
It seems that the issue is caused by the <PopUp Height=514/>
To test it, set the height to 0 to see if it fixes the gap. If so, you can bind the visibility to PopUp.IsOpen using a Visibility converter (I think Blue MVVM has one). Since I'm not very educated on Converters at the moment, I came up with a workaround.
public RecipeButton : INotifyPropertyChanged {
// Need to implement INotifyPropertyChanged logic on IsCheckedVisiblity for UI to be notified of visibility changes
public Visibility IsCheckedVisibility { get; set; }
private bool _IsChecked;
public bool IsChecked {
get { return _IsChecked };
set { _IsChecked = value;
this.IsCheckedVisibility = value == true ? Visiblity.Collapsed : Visiblity.Visible;
}
}
<PopUp Visibility = "{Binding IsCheckedVisibility}"/>
Let me know if that doesn't work and I'll try something else.
I have created some buttons by first filling a class containing get/set methods and then using that info in XAML.
C#
List<MediaDetail> movies = new List<MediaDetail>();
...
...
MovieListView.ItemsSource = movies;
XAML
<Window.Resources>
<DataTemplate x:Key="ItemTemplate">
<WrapPanel Orientation="Vertical" Width="Auto">
<Button Width="200" Height="300" Click="SelectMovie_Click" Name ="NEED THIS TO BE DYNAMIC">
<Button.Template>
<ControlTemplate>
<Image Source="{Binding image}"/>
</ControlTemplate>
</Button.Template>
</Button>
<TextBlock Text="{Binding title}" HorizontalAlignment="Center"/>
</WrapPanel>
</DataTemplate>
</Window.Resources>
<ListView Name="MovieListView" ItemTemplate="{StaticResource ItemTemplate}" ItemsSource="{Binding Path = movies}" Margin="0,0,0,0" SelectionChanged="MovieListView_SelectionChanged" Grid.Row="1">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="5" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
However the issue is, I need each button to have a unique id. I read elsewhere that this can't be done in XAML but I'm not sure how or where in my C# code to create these buttons.
I think the most flexible solution here would be to define a behavior:
public class UniqueNameBehavior : Behavior<FrameworkElement>
{
protected override void OnAttached()
{
base.OnAttached();
//assign unique name to the associated element, for example:
AssociatedObject.Name = Guid.NewGuid().ToString().Replace("-", null);
}
}
In XAML, attach this behavior to any element:
<Button>
<i:Interaction.Behaviors>
<local:UniqueNameBehavior/>
</i:Interaction.Behaviors>
</Button>
where xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" from System.Windows.Interactivity assembly.
I'm trying to name and then set a DataContext for a ComboBox that's embedded within an ItemsControl.
In my main Window, I can access the ItemsControl by name but not any of the elements within the ItemsControl.
public MainWindow()
{
InitializeComponent();
SList.DataContext = App.SCList; //this is valid
StS.DataContext = App.STList; //this is not
}
And here's the XAML code. I can access SList from within the program but not "StS" or "SPanel1". The compiler error is:
"The name 'StS' does not exist in the current context"
Note that I've tried both "Name" and "x:Name"
<ScrollViewer VerticalScrollBarVisibility="Auto" >
<ItemsControl Name ="SList" ItemsSource ="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Margin="12"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="3" CornerRadius="5" Margin="8,8" ClipToBounds="True" Background="Beige">
<StackPanel Name="SPanel1">
<StackPanel Orientation="Horizontal">
<Label Content="S Type"/>
<ComboBox Name ="StS" ItemsSource="{Binding Path=ShipType.TypeName, Mode=TwoWay}" DataContext="STList">
</ComboBox>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Class Name"/>
<TextBox Text="{Binding Path = Name, Mode=TwoWay}" HorizontalAlignment="Center" FontWeight="Bold" />
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
In my Windows Phone 8.1 Universal Application, I have a ListView with a button in the ItemTemplate, and I need to see when that button's ManipulationStarted and ManipulationCompleted events are fired. Currently ManipulationStarted works fine, but ManipulationCompleted does not. Could someone help explain why?
SnapsPage.xaml
<Grid Grid.Row="0" Background="#88686868">
<Grid.RowDefinitions>
<RowDefinition Height="130" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Vertical" Margin="20,0,0,0">
<TextBlock Text="{Binding Path=Manager.Account.Username, FallbackValue='loading...'}" Margin="0,12,0,0" Style="{ThemeResource HeaderTextBlockStyle}"/>
<TextBlock x:Uid="Snaps" Text="SNAPS" Style="{ThemeResource TitleTextBlockStyle}" Typography.Capitals="SmallCaps"/>
</StackPanel>
<ScrollViewer x:Name="ScrollViewer" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled"
Padding="0,0,0,20" Grid.Row="1" HorizontalContentAlignment="Stretch">
<ListView HorizontalContentAlignment="Stretch"
ItemsSource="{Binding Path=Manager.Account.Snaps}">
<ListView.ItemTemplate>
<DataTemplate>
<Button x:Name="ButtonSnap" Style="{StaticResource BasicEmptyButtonStyle}"
ManipulationStarting="ButtonSnap_OnManipulationStarting"
ManipulationCompleted="ButtonSnap_OnManipulationCompleted"
ManipulationMode="All"
Command="{Binding ElementName=ItemsControl, Path=DataContext.TryDownloadMediaCommand}"
CommandParameter="{Binding}">
<!-- Content Here -->
</Button>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel HorizontalAlignment="Stretch"></VirtualizingStackPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</ScrollViewer>
</Grid>
<Grid Grid.Row="0" x:Name="MediaGrid" Background="#FF000000" IsHitTestVisible="False">
<Image x:Name="MediaImage" IsHitTestVisible="False"/>
<Grid Width="45" Height="45" Background="#99000000"
VerticalAlignment="Top" HorizontalAlignment="Right" Margin="25">
<TextBlock Text="10" VerticalAlignment="Center" HorizontalAlignment="Center" FontFamily="Segoe WP Semibold" FontSize="24" />
</Grid>
</Grid>
SnapsPage.cs
private void ButtonSnap_OnManipulationStarting(object sender, ManipulationStartingRoutedEventArgs e)
{
Debug.WriteLine("ButtonSnap_OnManipulationStarting");
var button = sender as Button;
if (button == null) return;
var snap = button.DataContext as Snap;
if (snap == null) return;
_relevantSnap = snap;
_isFingerDown = true;
_scrollYIndex = ScrollViewer.VerticalOffset;
_holdingTimer.Start();
}
private void ButtonSnap_OnManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
{
Debug.WriteLine("ButtonSnap_OnManipulationCompleted");
_isFingerDown = false;
if (_isMediaOpen)
DisposeMediaTidily();
}
Note: I tested with Windows Phone 8.1 XAML App (not Silverlight)
The Button has a ManipulationMode of System which, according to the docs, means that you should not get manipulation events.
An element must have a ManipulationMode value other than None or System to be a manipulation event source