I'm reworking a very old control to a MVVM-kind of control. I have a list of alarms. When the user presses the button in the column header, I have to clear the list of visible alarms and scroll to the next alarm (so the first one which was not visible).
I created the button in the control template of the column header. The command property works but it return a NaN, so I expect that the binding of the command parameter to the Height of the visible part of the window is incorrect. When I debug the code behind, the property "Height" does hold a number.
The XAML:
<DataGrid x:Class="Kwa.Presentation.Views.AlarmList.AlarmList"
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:Kwa.Presentation.Views.AlarmList"
xmlns:components="clr-namespace:Kwa.Presentation.Components"
xmlns:converters="clr-namespace:Kwa.Presentation.Converters"
xmlns:Trans="clr-namespace:Kwa.Presentation.Resources"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="750"
ItemsSource="{Binding Alarms}"
SelectedItem="{Binding SelectedAlarm}"
IsSynchronizedWithCurrentItem="True"
CanUserResizeColumns="True" IsReadOnly="True" CanUserReorderColumns="False" CanUserSortColumns="False" SelectionMode="Single" CanUserAddRows="False"
Background="White" RowHeaderWidth="0" AutoGenerateColumns="False" GridLinesVisibility="None" RowHeight="{Binding Rowheight}" FrozenColumnCount = "1"
ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto"
x:Name="AlarmFramework"
SizeChanged="AlarmFramework_SizeChanged"
>
<Style TargetType="DataGridColumnHeader" x:Key="WithButt">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="DataGridColumnHeader">
<Border BorderBrush="Silver" BorderThickness="0 0 0 1"
Padding="5 0 0 0" Background="White">
<StackPanel Orientation="Horizontal" Margin="0">
<TextBlock Text="{Binding}"
VerticalAlignment="Center" FontWeight="Bold"/>
<Button Content="{x:Static Trans:TranslatedResources.AlarmAcceptContent}" Margin="60 3 10 3 " VerticalAlignment="Center" VerticalContentAlignment="Center" Padding="2"
Command="{Binding DataContext.AcknowledgeCommand, RelativeSource={RelativeSource AncestorType={x:Type local:AlarmList}}}" CommandParameter="{Binding Height, RelativeSource={RelativeSource AncestorType={x:Type local:AlarmList}}}" ToolTip="{x:Static Trans:TranslatedResources.AlarmAcceptTooltip}" Style="{StaticResource Butt}"/>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid>
The Code behind:
public partial class AlarmList : DataGrid
{
private double Height = 0;
public AlarmList()
{
InitializeComponent();
}
private void AlarmFramework_SizeChanged(object sender, SizeChangedEventArgs e)
{
Height = e.NewSize.Height;
}
}
The ViewModel:
public class AlarmListViewModel : MainViewModelBase
{
private readonly IActionCommand _acknowledgeCommand;
public IActionCommand AcknowledgeCommand
{
get { return _acknowledgeCommand; }
}
public AlarmListViewModel()
{
//Add command
_acknowledgeCommand = new ActionCommand<double>(p => Acknowledge(p));
}
private void Acknowledge(double parameter)
{
try
{
double DatagridWidth = (double)parameter;
int AmountAcknowledged = (int)Math.Floor(DatagridWidth / RowHeight);
int LastAlarmSent = Alarms[0].AlarmNumber + AmountAcknowledged;
_proxy.Send(LastAlarmSent);
SelectedAlarm = Alarms[LastAlarmSent + 1];
}
catch (Exception ex)
{
_viewManager.ShowDialog(new MessageDialogViewModel()
{
AskAnswer = false,
Text = ex.Message,
Title = TranslatedResources.AlarmAckSendErrorTitle,
});
}
}
}
I think if you initialize your property with usercontrol it will works
public AlarmList()
{
InitializeComponent();
Height = this.ActualHeight;
}
Or change your CommandParameter like this:
CommandParameter="{Binding ActualHeight .....
Related
I would like to create an animation within my grid. I have a 5x5 Grid, each grid shows a button. After the grid is loaded one of the buttons should randomliy change his color to green. After 1 seconds this button should change back and another should change his color to green.
If the user is able to reach the this button within this 1 second with his mouse (mouseover) the button should change his color to red and stay red. The next button who changes his color to green should not be this one.
This should be a little game. My question is, what is the easiest way to implement this game.
Please help me!
<Page x:Class="LeapTest.Layout"
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:LeapTest"
mc:Ignorable="d"
d:DesignHeight="1050" d:DesignWidth="1000"
Title="Layout">
<Page.Resources>
<Style x:Key="pageTitle" TargetType="TextBlock">
<Setter Property="Background" Value="DimGray"/>
<Setter Property="FontSize" Value="40"/>
<Setter Property="FontFamily" Value="Arial"/>
<Setter Property="TextAlignment" Value="Center"/>
<Setter Property="Padding" Value="0,5,0,5"/>
</Style>
<Style x:Key="Grid" TargetType="Grid">
<Setter Property="Background" Value="White"/>
</Style>
<Style x:Key="Button" TargetType="Button">
<Setter Property="Background" Value="White"/>
<Setter Property="BorderBrush" Value="Green"/>
<Setter Property="BorderThickness" Value="2"/>
</Style>
</Page.Resources>
<Grid Style="{StaticResource Grid}">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="200" />
<RowDefinition Height="200" />
<RowDefinition Height="200" />
<RowDefinition Height="200" />
<RowDefinition Height="200" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="200" />
<ColumnDefinition Width="200" />
<ColumnDefinition Width="200" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource pageTitle}"> LEAP Motion </TextBlock>
<Button Name ="BTN_0_0" Grid.Column="0" Grid.Row="1" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_0_1" Grid.Column="1" Grid.Row="1" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_0_2" Grid.Column="2" Grid.Row="1" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_0_3" Grid.Column="3" Grid.Row="1" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_0_4" Grid.Column="4" Grid.Row="1" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_1_0" Grid.Column="0" Grid.Row="2" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_1_1" Grid.Column="1" Grid.Row="2" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_1_2" Grid.Column="2" Grid.Row="2" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_1_3" Grid.Column="3" Grid.Row="2" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_1_4" Grid.Column="4" Grid.Row="2" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_2_0" Grid.Column="0" Grid.Row="3" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_2_1" Grid.Column="1" Grid.Row="3" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_2_2" Grid.Column="2" Grid.Row="3" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_2_3" Grid.Column="3" Grid.Row="3" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_2_4" Grid.Column="4" Grid.Row="3" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_3_0" Grid.Column="0" Grid.Row="4" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_3_1" Grid.Column="1" Grid.Row="4" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_3_2" Grid.Column="2" Grid.Row="4" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_3_3" Grid.Column="3" Grid.Row="4" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_3_4" Grid.Column="4" Grid.Row="4" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_4_0" Grid.Column="0" Grid.Row="5" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_4_1" Grid.Column="1" Grid.Row="5" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_4_2" Grid.Column="2" Grid.Row="5" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_4_3" Grid.Column="3" Grid.Row="5" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_4_4" Grid.Column="4" Grid.Row="5" Click="BTN_Click" Style="{StaticResource Button}"/>
</Grid>
//BackEnd Code
private void BTN_Click(object sender, RoutedEventArgs e)
{
//BTN1.Background = Brushes.Green;
Button btnTest = (Button)sender;
if (btnTest.Background == Brushes.Green)
{
btnTest.Background = Brushes.White;
}
else
{
btnTest.Background = Brushes.Green;
}
}
you should loop over all the button controls on the form with a special name or tag. Save this in an array and select one at random and change it's color. if the mouseover finds the one with the right color recall the method to find a control at random and so on.
This should be pretty straight forward. And since this looks like homework. I won't solve it for you as you wouldn't learn anything. Think about what needs to be done and google each part seperately. try to understand it and then continue! goodluck!
EDIT : regarding your question in a strict way, you do not need animation to acheive what you want to do, unless your need is to animate color changes.
Here is a full working example not using MVVM but using a collection of models representing the buttons.
The main window/page code handling all the logic (Random, model changes, etc.):
public partial class MainWindow : Window, INotifyPropertyChanged
{
private const int BTN_NUMBERS = 25;
private ObservableCollection<ButtonModel> _buttonsCollection;
private ButtonModel _currentTarget;
public ObservableCollection<int> ExcludedItems
{
get { return _excludedItems; }
private set { _excludedItems = value; OnPropertyChanged(); }
}
private Random _rnd;
private Timer _timer;
private ObservableCollection<int> _excludedItems = new ObservableCollection<int>();
public MainWindow()
{
DataContext = this;
InitializeComponent();
ButtonsCollection = new ObservableCollection<ButtonModel>();
for (int i = 0; i < BTN_NUMBERS; i++)
{
ButtonsCollection.Add(new ButtonModel() { ButtonNumber = i });
}
Start();
}
private void Start()
{
_currentTarget = null;
foreach (var bm in ButtonsCollection)
{
bm.IsCurrentTarget = bm.IsReached = false;
}
ExcludedItems.Clear();
_rnd = new Random(DateTime.Now.Second);
_timer = new Timer(OnTargetChanged, null, 0, 1000);
}
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<ButtonModel> ButtonsCollection
{
get { return _buttonsCollection; }
set { _buttonsCollection = value; OnPropertyChanged(); }
}
void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void Btn_candidate_OnMouseEnter(object sender, MouseEventArgs e)
{
ButtonModel model = ((Button)sender).DataContext as ButtonModel;
if (model != null && model.IsCurrentTarget && !ExcludedItems.Contains(model.ButtonNumber))
{
model.IsReached = true;
ExcludedItems.Add(model.ButtonNumber);
}
}
private void ChangeTarget()
{
var target = GetNextTarget();
if (target == -1)
{
if (_timer != null)
{
_timer.Dispose();
_timer = null;
}
MessageBox.Show("All items have been reached ! Congratulations");
}
if (_currentTarget != null) _currentTarget.IsCurrentTarget = false;
_currentTarget = ButtonsCollection[target];
_currentTarget.IsCurrentTarget = true;
}
private int GetNextTarget()
{
if (ExcludedItems.Count == BTN_NUMBERS)
{
return -1;
}
var target = _rnd.Next(0, BTN_NUMBERS);
if (ExcludedItems.Contains(target))
{
return GetNextTarget();
}
return target;
}
private void OnTargetChanged(object state)
{
this.Dispatcher.InvokeAsync(ChangeTarget);
}
private void Btn_startover_OnClick(object sender, RoutedEventArgs e)
{
Start();
}
}
The model representing the buttons, that contains the code that triggers XAML changes :
public class ButtonModel : INotifyPropertyChanged
{
private bool _isCurrentTarget;
private bool _isReached;
public event PropertyChangedEventHandler PropertyChanged;
public int ButtonNumber { get; set; }
public bool IsCurrentTarget
{
get { return _isCurrentTarget; }
set { _isCurrentTarget = value; OnPropertyChanged(); }
}
public bool IsReached
{
get { return _isReached; }
set { _isReached = value; OnPropertyChanged(); }
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And finally, and most importantly, the simplified XAML code :
<Window x:Class="GridAnimame.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:GridAnimame"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance {x:Type local:MainWindow}}"
Title="MainWindow" Height="500" Width="500">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="400"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="400"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ItemsControl ItemsSource="{Binding ButtonsCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Name="btn_candidate" MouseEnter="Btn_candidate_OnMouseEnter" Margin="1">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border x:Name="brd_btn_layout" Background="LightGray" BorderBrush="Black" BorderThickness="1">
<TextBlock Text="{Binding ButtonNumber}" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Black"></TextBlock>
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsCurrentTarget}" Value="true">
<Setter TargetName="brd_btn_layout" Property="Background" Value="Green"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding IsReached}" Value="true">
<Setter TargetName="brd_btn_layout" Property="Background" Value="Red"></Setter>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ListBox Grid.Row="0" Grid.Column="1" ItemsSource="{Binding ExcludedItems}"></ListBox>
<Button x:Name="btn_startover" Grid.Row="1" Grid.Column="1" Content="Start Over !" Margin="2" Click="Btn_startover_OnClick"></Button>
</Grid>
Althought the logic could be improved, here are the key points of this sample :
You don't declare all the buttons on a static way in the XAML. Instead, your main model holds a collection of models representing the buttons and containing all the data that will trigger the XAML (visual) changes. Thus, the control in XAML representing the grid is bound to this collection
The logic is made throught code and the visual effect of this loggic is reflected using triggers declared in XAML
Only 2 things still have to be done to have a clean approach : 1)The main window logic can now be easily be encapsulated into a real view model which will serve as the window data context and 2) the events should be replaced by DelegateCommands
In my Windows store app there is a list view which is using an item source to get data. It looks like this:
<ListView x:Name="lsvLinks" IsItemClickEnabled="True"
SelectionMode="Single"
ItemsSource="{Binding Source={StaticResource cvs2}}" ItemClick="lsvLinks_ItemClick" >
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid Orientation="Vertical" HorizontalChildrenAlignment="left"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="-7.5"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Width="340" Height="32" Background="#FFBE9CDE" HorizontalAlignment="Left">
<StackPanel Width="255" VerticalAlignment="Center" Margin="15,0,0,0">
<TextBlock Text="{Binding Link}" Foreground="{Binding Color}" FontSize="15" Margin="0,3,0,0" FontWeight="Normal" VerticalAlignment="Center" HorizontalAlignment="Left"/>
</StackPanel>
<StackPanel Width="50" VerticalAlignment="Center" Margin="0,0,0,0">
<Button x:Name="btnRemove" Width="30" Height="30" Margin="20,0,0,0" ToolTipService.ToolTip="Remove" Click="btnRemove_click">
<Button.Template>
<ControlTemplate>
<Image Source="Assets/cancel.png" Width="30" Height="30"/>
</ControlTemplate>
</Button.Template>
</Button>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
My C# code
try {
IEnumerable < linkTable > obsCollection = (IEnumerable < linkTable > ) await webservice.getLinksStudentAsync(1);
linkList = new List < linkTable > (obsCollection);
int count = 1;
foreach(linkTable linkL in linkList) {
if (linkL.status.Equals("yes")) {
links.Add(new Collection {
ID = count, Link = linkL.link, Type = "Accept", Color = "green", BackColor = "#FFA27BC7"
});
} else if (linkL.status.Equals("no")) {
links.Add(new Collection {
ID = count, Link = linkL.link, Type = "Reject", Color = "Red", BackColor = "#FFA27BC7"
});
} else {
links.Add(new Collection {
ID = count, Link = linkL.link, Type = "Pending", Color = "White", BackColor = "#FFA27BC7"
});
}
count++;
}
cvs2.Source = links;
}
When a user selects an item in the listview, I need to retrieve its ID. But I don't understand how to do that. Can anyone tell me how to do that?
You'll need to add a SelectionChanged event to the ListView and implement it.
public void ItemSelected(object sender, SelectionChangedEventArgs args)
{
var item= lsvLinks.SelectedItem as Collection;
int ID = item.ID;
}
On your ListView you could add the event as below.
<ListView x:Name="lsvLinks" IsItemClickEnabled="True" SelectionMode="Single" ItemsSource="{Binding Source={StaticResource cvs2}}" SelectionChanged="ItemSelected" >
I have a Button that when clicked should adjust the Widths of two Grids.
<DataTemplate x:Key="ItemTemplate">
<DockPanel Width="Auto">
<Button Click="SelectMovie_Click" DockPanel.Dock="Top">
<Button.Template>
<ControlTemplate >
<Image Source="{Binding image}"/>
</ControlTemplate>
</Button.Template>
<i:Interaction.Behaviors>
<local:UniqueNameBehavior ID="{Binding id}"/>
</i:Interaction.Behaviors>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding ShowRightGridCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<TextBlock Text="{Binding title}" HorizontalAlignment="Center" DockPanel.Dock="Bottom"/>
</DockPanel>
</DataTemplate>
This Button is displayed within a Grid as such:
<Grid Grid.Row="2" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding LeftGridWidth}" />
<ColumnDefinition Width="{Binding RightGridWidth}" />
</Grid.ColumnDefinitions>
// BUTTONS DISPLAYED HERE
<Grid x:Name="LeftGrid" Grid.Row="2" Grid.Column="0" >
<Border BorderThickness="1" BorderBrush="Red">
<ItemsControl ItemTemplate="{StaticResource ItemTemplate}" ItemsSource="{Binding _movies}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="5" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Border>
</Grid>
<Grid x:Name="RightGrid" HorizontalAlignment="Left" Grid.Row="2" Grid.Column="1" >
<DockPanel>
<StackPanel VerticalAlignment="Top" Height="200">
<TextBlock Width="200" Height="50" DockPanel.Dock="Top" HorizontalAlignment="Left">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} ({1})">
<Binding Path = "SelectedMovie.title"/>
<Binding Path = "SelectedMovie.year"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
<StackPanel VerticalAlignment="Top" Height="200">
<Grid HorizontalAlignment="Center">
<Image Source="star_icon.png" Width="100" Height="100" VerticalAlignment="Top"/>
<TextBlock Text="{Binding SelectedMovie.rating}" Style="{StaticResource AnnotationStyle}" Width="150"/>
</Grid>
</StackPanel>
</DockPanel>
</Grid>
</Grid>
ViewModel
public List<MediaDetail> _movies { get; set; }
public string selectedMovieID { get; set; }
private GridLength _leftGridWidth;
private GridLength _rightGridWidth;
private readonly GridLength _defaultRightGridWidth = new GridLength(0, GridUnitType.Pixel);
public MoviePanelViewModel()
{
ShowRightGridCommand = new DelegateCommand(SwitchRightGridWidth);
LeftGridWidth = new GridLength(7, GridUnitType.Star);
RightGridWidth = _defaultRightGridWidth;
}
private void SwitchRightGridWidth()
{
RightGridWidth = RightGridWidth == _defaultRightGridWidth ? new GridLength(3, GridUnitType.Star) : _defaultRightGridWidth;
}
public GridLength LeftGridWidth
{
get { return _leftGridWidth; }
set { _leftGridWidth = value; OnPropertyChanged("LeftGridWidth"); }
}
public GridLength RightGridWidth
{
get { return _rightGridWidth; }
set { _rightGridWidth = value; OnPropertyChanged("RightGridWidth"); }
}
public ICommand ShowRightGridCommand { get; set; }
The problem is that when I run my code, I get the error:
BindingExpression path error: 'ShowRightGridCommand' property not found on 'object' ''MediaDetail'
I am not sure how to structure my code such that the Width properties for the Grids can remain in the ViewModel but the Trigger for my Button also works. The DataContext for my View is the ViewModel.
Is there a good way to achieve this?
Thank you for your help.
The problem is your binding inside a DataTemplate: WPF tries to find the property path ShowRightGridCommand on your MediaDetail class, because the ItemsControl sets the data context for each item container it generates to the corresponding item.
What you actually want to do is binding to the view model. You can do this like so:
{Binding ElementName=LeftGrid, Path=DataContext.ShowRightGridCommand}
LeftGrid is an element outside of the ItemsControl. Its DataContext is your MoviePanelViewModel instance, and there is your ShowRightGridCommand property defined.
A little cleaner would be to put the name root on your view's root control (usually a UserControl or Window), and then use ElementName=root,Path=DataContext.... as your binding - at least that's what I usually do.
An alternative would be to use RelativeSource={RelativeSource UserControl}, but I find ElementName=root to be simpler.
I know there are a lot of question like this in the web, but believe me I spent on this a lot of hours and I still not success, I really will glad for any help!
I loading various images in run time and I want to show them in list box (small images, then the user should click on one of them and show him in real size).
my code is:
public partial class MainWindow : Window
{
int imageNumber = 0;
public List<String> ImagePath = new List<String>();
public MainWindow()
{
InitializeComponent();
lb_Images.ItemsSource = ImagePath;
}
private void bu_addImage_Click(object sender, RoutedEventArgs e)
{
addImageToListBox();
}
private void addImageToListBox()
{
imageNumber++;
if (imageNumber == 4) imageNumber = 0;
string directoryPath = AppDomain.CurrentDomain.BaseDirectory;
// load input image
string ImageFilename = directoryPath + "img";
ImageFilename += imageNumber.ToString();
ImageFilename += ".jpg";
ImagePath.Add(ImageFilename);
}
}
and the xaml is:
<Window x:Class="forQuestionWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="216" Width="519">
<Window.Resources>
<DataTemplate x:Key="ImageGalleryDataTemplate">
<Grid>
<Border BorderBrush="#FFFF9800" BorderThickness="1" Width="120" Height="120" Padding="5" Margin="5" CornerRadius="6">
<!--Bind Image Path in Image Control-->
<Image Source="{Binding ImagePath}" Stretch="Fill" HorizontalAlignment="Center">
<!--View Large Image on Image Control Tooltip-->
<Image.ToolTip>
<Grid>
<Image Source="{Binding ImagePath}" Stretch="Fill" HorizontalAlignment="Center" Height="200" Width="200"></Image>
</Grid>
</Image.ToolTip>
</Image>
</Border>
</Grid>
</DataTemplate>
<ItemsPanelTemplate x:Key="ImageGalleryItemsPanelTemplate">
<!--Display Images on UniformGrid Panel-->
<UniformGrid Rows="1" Columns="25" HorizontalAlignment="Center" VerticalAlignment="Stretch"/>
</ItemsPanelTemplate>
</Window.Resources>
<Grid>
<Canvas Height="177" HorizontalAlignment="Left" Name="canvas1" VerticalAlignment="Top" Width="497">
<ListBox Canvas.Left="6" Canvas.Top="5" Height="166" Name="lb_Images" Width="441"
BorderBrush="{x:Null}" DataContext="{Binding Source={StaticResource ImageGalleryDataTemplate}}"
ItemsSource="{Binding Source={StaticResource ImageGalleryItemsPanelTemplate}}">
</ListBox>
<Button Canvas.Left="453" Canvas.Top="26" Content="Add" Height="64" Name="bu_addImage" Width="38" Click="bu_addImage_Click" />
</Canvas>
</Grid>
</Window>
I know that the list box updated when I add image path to the list because if I debug I found some items under lb_Images.items, but I show nothing.
I'll glad for any help! thank you!!
Some notes
DataContext for ListBox it's not necessary, then you set ItemSource. Instead of set the ItemTemplate
In DataTemplate remove the {Binding ImagePath}, instead of write {Binding}, because in this case the elements of the DataTemplate inherit DataContext.
When you add new items to ListBox.Items, you must call ListBox.Items.Refresh() or use the ObservableCollection<T>, because:
ObservableCollection represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
Try this example:
XAML
<Window.Resources>
<DataTemplate x:Key="ImageGalleryDataTemplate">
<Grid>
<Border BorderBrush="#FFFF9800" BorderThickness="1" Width="120" Height="120" Padding="5" Margin="5" CornerRadius="6">
<Image Source="{Binding}" Stretch="Fill" HorizontalAlignment="Center">
<Image.ToolTip>
<Grid>
<Image Source="{Binding}" Stretch="Fill" HorizontalAlignment="Center" Height="200" Width="200" />
</Grid>
</Image.ToolTip>
</Image>
</Border>
</Grid>
</DataTemplate>
<ItemsPanelTemplate x:Key="ImageGalleryItemsPanelTemplate">
<UniformGrid Rows="1" Columns="25" HorizontalAlignment="Center" VerticalAlignment="Stretch"/>
</ItemsPanelTemplate>
</Window.Resources>
<Grid>
<Canvas Height="177" HorizontalAlignment="Left" Name="canvas1" VerticalAlignment="Top" Width="497">
<ListBox Canvas.Left="6" Canvas.Top="5" Height="166" Name="lb_Images" Width="441"
ItemTemplate="{StaticResource ImageGalleryDataTemplate}"
ItemsSource="{Binding Path=ImagePath}">
</ListBox>
<Button Canvas.Left="453" Canvas.Top="26" Content="Add" Height="64" Name="bu_addImage" Width="38" Click="bu_addImage_Click" />
</Canvas>
</Grid>
Code-behind
public partial class MainWindow : Window
{
int imageNumber = 0;
public List<String> ImagePath = new List<String>();
public MainWindow()
{
InitializeComponent();
lb_Images.ItemsSource = ImagePath;
}
private void bu_addImage_Click(object sender, RoutedEventArgs e)
{
addImageToListBox();
}
private void addImageToListBox()
{
imageNumber++;
if (imageNumber == 4) imageNumber = 0;
string directoryPath = AppDomain.CurrentDomain.BaseDirectory;
// load input image
string ImageFilename = directoryPath + "img";
ImageFilename += imageNumber.ToString();
ImageFilename += ".jpg";
ImagePath.Add(ImageFilename);
lb_Images.Items.Refresh();
}
}
My View is clickable like a radiobutton with about 7 other of these radiobuttons, but my listbox is not updating it's selected property unless I click outside of my radiobutton.
Basically I have a AbstractTask as my base. I have 7 child classes. I want to be able to select one of these AbstractTasks. That's all i'm after. So in my main window i have this.
<Window x:Class="AdvancedTaskAssigner.View.MainWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:AdvancedTaskAssigner.View"
Title="MainWindowView" Height="300" Width="300" SizeToContent="Height">
<DockPanel>
<TextBlock Text="TextBlock" DockPanel.Dock="Top" />
<TextBlock Text="{Binding ElementName=listTasks, Path=SelectedItem.Name}" DockPanel.Dock="Top" />
<ListBox x:Name="listTasks" ItemsSource="{Binding Tasks}" HorizontalContentAlignment="Stretch" SelectedItem="{Binding IsSelected}">
<ListBox.ItemTemplate>
<DataTemplate>
<v:AbstractTaskView />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</Window>
since this is a library and not a application I had to put this in the Constructor of MainWindowView
MainWindowView.xaml.cs
public MainWindowView()
{
InitializeComponent();
var atvm = new ViewModel.MainWindowViewModel();
atvm.LoadTasks();
this.DataContext = atvm;
}
MainWindowViewModel.cs
class MainWindowViewModel
{
internal void LoadTasks()
{
var assembly = Assembly.GetAssembly(typeof(AbstractTask)).GetTypes().Where(t => t.IsSubclassOf(typeof(AbstractTask)));
Type[] typelist = GetTypesInNamespace(Assembly.GetAssembly(typeof(AbstractTask)), typeof(AbstractTask));
foreach (Type t in typelist)
{
if(!t.IsAbstract && t.BaseType.Equals(typeof(AbstractTask)))
{
tasks.Add(new AbstractTaskViewModel(t));
}
}
}
private Type[] GetTypesInNamespace(Assembly assembly, Type baseClass)
{
return assembly.GetTypes().Where(t => t.IsSubclassOf(baseClass)).ToArray();
}
private ObservableCollection<AbstractTaskViewModel> tasks = new ObservableCollection<AbstractTaskViewModel>();
public ObservableCollection<AbstractTaskViewModel> Tasks
{
get { return tasks; }
}
}
AbstractTaskViewModel.cs
public class AbstractTaskViewModel
{
public AbstractTaskViewModel(Type task)
{
if (!task.IsSubclassOf(typeof(AbstractTask)))
{
throw new NotSupportedException(string.Format("{0} is not a subclass of AbstractTask", task.Name));
}
Task = task;
}
public string Description
{
get
{
return GetCustomAttribute(0);
}
}
public string Name
{
get
{
return GetCustomAttribute(1);
}
}
public bool IsSelected{get;set;}
private string GetCustomAttribute(int index)
{
var descriptions = (DescriptionAttribute[])Task.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (descriptions.Length == 0)
{
return null;
}
return descriptions[index].Description;
}
protected readonly Type Task;
}
AbstractTaskView.xaml
<RadioButton
x:Class="AdvancedTaskAssigner.View.AbstractTaskView"
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"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" GroupName="AbstractTasks" Background="Transparent" IsChecked="{Binding IsSelected, Mode=TwoWay}">
<RadioButton.Template>
<ControlTemplate TargetType="{x:Type RadioButton}">
<Grid>
<Border x:Name="MyHead" BorderBrush="Black" BorderThickness="2" CornerRadius="5" Background="LightGray" Margin="20,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Panel.ZIndex="2">
<TextBlock Text="{Binding Name}" Margin="5,2" MinWidth="50" />
</Border>
<Border x:Name="Myoooo" BorderBrush="Black" BorderThickness="2" CornerRadius="5" Background="Transparent" Margin="0,10,0,0" Panel.ZIndex="1">
<TextBlock Text="{Binding Description}" Margin="5,15,5,2" />
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="MyHead" Property="Background" Value="LightGreen" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</RadioButton.Template>
</RadioButton>
this is what i am getting..
I want the green border to be the selected item. not what the Listbox's Selected item is. The bottom text box is the name of the selected item.
There are some issues related to binding IsChecked property of RadioButton. You can read about it here or here. You can apply the solution presented in the first link using your AbstractTaskView instead of RadioButton. Replace your listbox in MainWindowView with the code:
<ListBox x:Name="listTasks" ItemsSource="{Binding Tasks}" HorizontalContentAlignment="Stretch">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<v:AbstractTaskView Content="{TemplateBinding Content}"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsSelected}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
You have to also remove the folowing part of code from AbstractTaskView:
IsChecked="{Binding IsSelected, Mode=TwoWay}"
I hope you don't need IsSelected property of AbstractTaskViewModel to be set. But if you do, you can set it for example in Checked event handler in AbstractTaskView code behind (I know it's not pretty solution).
I see you're binding the IsSelected (boolean) to the SelectedItem (object)