I try to use System.Windows.Interactivity to bind mouse events of elements on the screen to some command logic.
I have a simple Canvas with three circles. A command is implemented that decreases the radius of the circles. This works fine when bound to the command property of a Button.
Unfortunately, when I try to bind this command to the PreviewMouseDown event of the Canvas, it doesn't work anymore. What am I missing?
Here is the MainWindow.xaml:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:test="clr-namespace:Test"
Title="MainWindow" Height="550" Width="525">
<Window.Resources>
<test:ViewModel x:Key="viewobj"/>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding CircleItems, Source={StaticResource viewobj}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Black" ClipToBounds="True" HorizontalAlignment="Left" Height="400" Margin="50,20,0,0" VerticalAlignment="Top" Width="400">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseDown" >
<i:InvokeCommandAction Command="{Binding StartCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Width="{Binding Radius}" Height="{Binding Radius}" Fill="Red"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Button" Command="{Binding StartCommand, Source={StaticResource viewobj}}" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="189,474,0,0"/>
</Grid>
</Window>
The MainWindow.xaml.cs is empty except for initialization in accordance to MVVM principles:
using System.Windows;
namespace Test
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
This is the ViewModel.cs:
using System.Collections.ObjectModel;
using System.ComponentModel;
using Test.Model;
namespace Test
{
public class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<CircleItem> CircleItems { get; set; }
private ButtonCommand _StartCommand;
public ButtonCommand StartCommand
{
get { return _StartCommand; }
}
public ViewModel()
{
_StartCommand = new ButtonCommand(UpdateMap, () => {return true;});
CircleItems = new ObservableCollection<CircleItem>();
CircleItems.Add(new CircleItem(20, 20, 40));
CircleItems.Add(new CircleItem(60, 60, 50));
CircleItems.Add(new CircleItem(120, 100, 30));
}
public void UpdateMap()
{
CircleItem.UpdateMap(CircleItems);
}
internal void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null)
{ PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
The CircleItem.cs class:
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace Test.Model
{
public class CircleItem : INotifyPropertyChanged
{
private double _x;
public double X
{
get { return _x; }
set
{
if (_x != value)
{
_x = value;
RaisePropertyChanged("X");
}
}
}
private double _y;
public double Y
{
get { return _y; }
set
{
if (_y != value)
{
_y = value;
RaisePropertyChanged("Y");
}
}
}
private double _radius;
public double Radius
{
get { return _radius; }
set
{
if (_radius != value)
{
_radius = value;
RaisePropertyChanged("Radius");
}
}
}
public CircleItem(double x, double y, double radius)
{
this.X = x;
this.Y = y;
this.Radius = radius;
}
public static void UpdateMap(ObservableCollection<CircleItem> coll)
{
foreach (var item in coll)
{
item.Radius -= 1;
}
}
internal void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null)
{ PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
And a simple RelayCommand.cs class:
using System;
using System.Windows.Input;
namespace Test
{
public class ButtonCommand : ICommand
{
private Action WhattoExecute;
private Func<bool> WhentoExecute;
public ButtonCommand(Action What, Func<bool> When)
{
WhattoExecute = What;
WhentoExecute = When;
}
public bool CanExecute(object parameter)
{
return WhentoExecute();
}
public void Execute(object parameter)
{
WhattoExecute();
}
public event EventHandler CanExecuteChanged;
}
}
Please note that the NuGet Package "System.Windows.Interactivity v4.0 for WPF" has to be installed for this example to work.
You forgot to set the Source of the Binding:
<Canvas Background="Black" ClipToBounds="True" HorizontalAlignment="Left" Height="400" Margin="50,20,0,0" VerticalAlignment="Top" Width="400">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseDown">
<i:InvokeCommandAction Command="{Binding StartCommand, Source={StaticResource viewobj}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Canvas>
Related
I'm a newbie in wpf and i know that this question has been asked other times, and i tried to implement some solutions that i found. But it's not working. I'm doing something wrong but i can't see what it is.
I've created a new simple application to test this problem.
namespace WpfApp3
{
public class MyElement
{
public string Text { get; set; }
public MyElement(string t)
{
Text = t;
}
}
public class MyCommand : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_handler(parameter);
}
private Action<object> _handler;
public MyCommand(Action<object> handler) { _handler = handler; }
}
public class MyItemsControlViewModel
{
ObservableCollection<MyElement> _items;
public ObservableCollection<MyElement> MyElementItems { get { return _items; } set { _items = value; RaisePropertyChanged("MyElementItems"); } }
ObservableCollection<MyElement> _temporayList;
private ICommand _itemClicked;
public ICommand ItemClicked { get { return _itemClicked; } }
public MyItemsControlViewModel()
{
_items = new ObservableCollection<MyElement>();
_temporayList = new ObservableCollection<MyElement>();
_itemClicked = new MyCommand(OnItemSelected);
AddItem("Element 1");
AddItem("Element 2");
AddItem("Element 3");
UpdateList();
}
public void UpdateList()
{
MyElementItems = _temporayList;
}
public void AddItem(string t)
{
MyElement item = new MyElement(t);
_temporayList.Add(item);
}
public void OnItemSelected(object param)
{
Debug.WriteLine("Executed!");
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
XAML
<UserControl x:Class="WpfApp3.MyUserControl"
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:WpfApp3"
mc:Ignorable="d"
d:DesignHeight="1080" d:DesignWidth="570"
x:Name="myCustomControl">
<Grid >
<Button x:Name="btnOutsideItemsControl" Width="100" Height="100 " VerticalAlignment="Top" Command="{Binding ItemClicked}" />
<ItemsControl
x:Name="listItems"
ScrollViewer.PanningMode="None"
IsEnabled="False"
Background = "Transparent"
HorizontalAlignment="Center"
ItemsSource="{Binding MyElementItems}" Margin="0,152,0,0" Width="549">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Left" Margin="50,0,0,0"
Background="Transparent" Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
Content="{Binding Text}"
Command="{Binding ElementName=listItems, Path=DataContext.ItemClicked}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
The component is used in MainWindow.xaml.
namespace WpfApp3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MyItemsControlViewModel _myViewModel;
public MyItemsControlViewModel MyViewModel { get { return _myViewModel; } }
public MainWindow()
{
_myViewModel = new MyItemsControlViewModel();
InitializeComponent();
myCustomControl.DataContext = MyViewModel;
}
}
}
XAML
<Window x:Class="WpfApp3.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:WpfApp3"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<local:MyUserControl x:Name="myCustomControl"/>
</Grid>
</Window>
When i run the application i can see correctly the list of 3 items with the correct text.
But if i click on one of the button of the list i can't see the output of Debug.WriteLine("Executed!");
But if i click on the button btnOutsideItemsControl that is outside the ItemsControl, it works. I can see the output of Debug.WriteLine("Executed!");
So i think that also the definition of the command is correct.
To bind correctly the Command property of Button inside the ItemsControl i try this
<Button Command="{Binding ElementName=listItems, Path=DataContext.ItemClicked}">
And also this
<Button Command="{Binding DataContext.ItemClicked, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ItemsControl}}">
But it not works.
Please help!
You're gonna kick yourself once I tell you.
Your problem is that you set IsEnabled="False" on your ItemsControl. Remove it and all will be well with the universe.
I've spent the last days reading and trying to apply the Navigation pattern from this page: https://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/
Now, after I got my project to work I'm really confused about how the binding works here. At first I have to clarify that I don't want a Navigation pane which is always visible like in the given example. I just want to use my MainView for navigation and each "SubView" should be able to go back to it's "parent" only.
Here's what I've got:
Project: APP
Class: App.xaml.cs
protected override void OnStartup(StartupEventArgs e) {
base.OnStartup(e);
UI.View.Main.MainView app = new UI.View.Main.MainView();
UI.View.Main.MainViewModel viewModel = new UI.View.Main.MainViewModel(some dependencies);
app.DataContext = viewModel;
app.Show();
}
ViewModel Base Class
public abstract class BaseViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private string _name;
public string Name {
get {
return _name;
}
set {
if (Name != value) {
_name = value;
OnPropertyChanged("Name");
}
}
}
private BaseViewModel _homePage;
public BaseViewModel HomePage {
get {
return _homePage;
}
set {
if (HomePage != value) {
_homePage = value;
OnPropertyChanged("HomePage");
}
}
}
public void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler temp = PropertyChanged;
if (temp != null) {
temp(this, new PropertyChangedEventArgs(propertyName));
}
}
}
MainViewModel
namespace SGDB.UI.View.Main {
public class MainViewModel : BaseViewModel {
private BaseViewModel _currentPageViewModel;
public BaseViewModel CurrentPageViewModel {
get {
return _currentPageViewModel;
}
set {
if (CurrentPageViewModel != value) {
_currentPageViewModel = value;
OnPropertyChanged("CurrentPageViewModel");
}
}
}
public List<BaseViewModel> PageViewModels { get; private set; }
public RelayCommand ChangePageCommand {
get {
return new RelayCommand(p => ChangeViewModel((BaseViewModel)p), p => p is BaseViewModel);
}
}
//Some Dependencies
public List<BaseViewModel> ViewPages { get; private set; }
public MainViewModel(some dependencies) {
HomePage = new HomeViewModel() { Name = "TEST" };
//assign dependencies
var uavm = new UserAdministration.UserAdministrationViewModel(_userUnitOfWork, _personUnitOfWork) {
Name = Resources.Language.Sys.UserAdministartionTitle
};
PageViewModels = new List<BaseViewModel>();
PageViewModels.Add(uavm);
ChangeViewModel(HomePage);
}
public void ChangeViewModel(BaseViewModel viewModel) {
CurrentPageViewModel = viewModel;
}
}
}
MainView
<Window x:Class="SGDB.UI.View.Main.MainView"
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:SGDB.UI.View.Main"
xmlns:ua="clr-namespace:SGDB.UI.View.UserAdministration"
xmlns:home="clr-namespace:SGDB.UI.View.Home"
mc:Ignorable="d"
Title="MainView" Height="400" Width="800">
<Window.Resources>
<DataTemplate DataType="{x:Type home:HomeViewModel}">
<home:Home/>
</DataTemplate>
<DataTemplate DataType="{x:Type ua:UserAdministrationViewModel}">
<ua:UserAdministration/>
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding CurrentPageViewModel}"/>
HomeViewModel
public class HomeViewModel : BaseViewModel {
public RelayCommand TestCommand {
get {
return new RelayCommand((x) => MessageBox.Show(x.ToString()), (x) => true);
}
}
}
HomeView
<UserControl x:Class="SGDB.UI.View.Home.Home"
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:SGDB.UI.View.Home"
xmlns:controls="clr-namespace:SGDB.UI.Controls"
xmlns:resx="clr-namespace:SGDB.UI.Resources.Language"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="800">
<Grid>
<Grid.Resources>
<Style TargetType="controls:ModernButton">
<Setter Property="Margin" Value="1"/>
<Setter Property="FontFamily" Value="Bosch Office Sans"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Size" Value="155"/>
</Style>
</Grid.Resources>
<Grid.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Color="#26688B" Offset="1"/>
<GradientStop Color="#11354C" Offset="0"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Grid.Background>
<Grid.RowDefinitions>
<RowDefinition Height="60"/>
<RowDefinition Height="3*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="150"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Grid.Column="0" HorizontalAlignment="Center">
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontFamily" Value="Bosch Office Sans"/>
</Style>
</StackPanel.Resources>
<TextBlock Text="{x:Static resx:Sys.ApplicationTitle}" FontSize="20" FontWeight="Bold" Margin="5"/>
<TextBlock Text="{x:Static resx:Sys.ApplicationSubTitle}" FontSize="12" FontWeight="Light"/>
</StackPanel>
<WrapPanel Grid.Row="1"
Grid.Column="0"
FlowDirection="LeftToRight"
HorizontalAlignment="Left"
Width="367">
<ItemsControl ItemsSource="{Binding DataContext.PageViewModels, RelativeSource={RelativeSource AncestorType={x:Type Window}}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:ModernButton Background="Dark"
Text="{Binding Name}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="{Binding Name}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding HomePage}"/>
// This Button is always disabled although HomePage is of Type HomeViewModel which is based on BaseViewModel.
</WrapPanel>
</Grid>
My questions are:
Why does the HomeView knot that the HomeViewModel is it's ViewModel? I do not define it anywhere in my code.
Why does the Binding on the Name Property work but binding to the HomePage Property doesn't? Both of them are defined in the BaseViewModel class.
Update 1:
RelayCommand class:
public class RelayCommand : ICommand {
public event EventHandler CanExecuteChanged;
readonly Action<object> _action;
readonly Predicate<object> _predicate;
public RelayCommand(Action<object> action, Predicate<object> predicate) {
_action = action;
_predicate = predicate;
}
public RelayCommand(Action<object> action) {
_action = action;
_predicate = ((x) => true);
}
public bool CanExecute(object parameter) {
return _predicate(parameter);
}
public void Execute(object parameter) {
_action(parameter);
}
}
Update 2:
What's the actual problem?
<Button Content="{Binding Name}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding HomePage}"/>
The Content gets bound properly but the CommandParameter (HomePage) which should be of Type BaseViewModel won't get validated through the Command's CanExecute. Both the Properties, Name and HomePage are defined inside the BaseViewModel.
Update 3:
<Button Content="{Binding Name}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding DataContext.HomePage, ElementName=Test}"/>
In your there is the next lines:
<DataTemplate DataType="{x:Type home:HomeViewModel}">
<home:Home/>
</DataTemplate>
meaning that the visual form of HomeViewModel is Home.
Your Binding works fine, I think your problem is the command itself. I don't know what is RelayCommand but i think your bug is from there.
RelayCommand should be something like this:
public abstract class BaseViewModel : INotifyPropertyChanged
{
private ICommand _f1KeyCommand;
public ICommand F1KeyCommand
{
get
{
if (_f1KeyCommand == null)
_f1KeyCommand = new DelegateCommand(F1KeyCommandCallback, CanExecute);
return _f1KeyCommand;
}
}
/// <summary>
/// Fired if F1 is pressend and 'CanExecute' returns true
/// </summary>
private void F1KeyCommandCallback(object obj)
{
Console.WriteLine("F1KeyCommandCallback fired");
}
// ....
}
This class allows delegating the commanding logic to methods passed as parameters,and enables a View to bind commands to objects that are not part of the element tree:
public class DelegateCommand : ICommand
{
#region Data Members
private Action<object> execute;
private Predicate<object> canExecute;
private event EventHandler CanExecuteChangedInternal;
#endregion
#region Ctor
public DelegateCommand(Action<object> execute)
: this(execute, DefaultCanExecute)
{
}
public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
if (canExecute == null)
{
throw new ArgumentNullException("canExecute");
}
this.execute = execute;
this.canExecute = canExecute;
}
#endregion
#region Properties
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
this.CanExecuteChangedInternal += value;
}
remove
{
CommandManager.RequerySuggested -= value;
this.CanExecuteChangedInternal -= value;
}
}
#endregion
#region Public Methods
public bool CanExecute(object parameter)
{
return this.canExecute != null && this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
public void OnCanExecuteChanged()
{
EventHandler handler = this.CanExecuteChangedInternal;
if (handler != null)
{
handler.Invoke(this, EventArgs.Empty);
}
}
public void Destroy()
{
this.canExecute = _ => false;
this.execute = _ => { return; };
}
#endregion
#region Private Methods
private static bool DefaultCanExecute(object parameter)
{
return true;
}
#endregion
}
In your view:
<controls:ModernButton Background="Dark"
Text="{Binding Name}"
Command="{Binding F1KeyCommand"
CommandParameter="{Binding}"/>
Sorry if this is hard to read but I can't figure it out. I think the problem is with my xaml but I have no idea. All I want it to do is to display 180 in the weight text box and 5 in the height text box. Thanks for any advice and let me know if more info is needed.
This is my MainWindow.xamml.cs
namespace Simple_BMI.Views
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
}
This is my MinWindow.xaml
<Window x:Class="Simple_BMI.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="300">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<Label>Weight: </Label>
<TextBox Text="{Binding Model.Weight}" Width="136" />
<Button>Update</Button>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label>Height: </Label>
<TextBox Text="{Binding Model.Height}" Width="136" />
</StackPanel>
</StackPanel>
</Window>
My model:
namespace Simple_BMI.Models
{
public class Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Model(double weight, double height)
{
Weight = weight;
Height = height;
}
private double _Weight;
public double Weight
{
get
{
return _Weight;
}
set
{
_Weight = value;
OnPropertyChanged("Weight");
}
}
private double _Height;
public double Height
{
get
{
return _Height;
}
set
{
_Height = value;
OnPropertyChanged("Height");
}
}
private void OnPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
And my view model:
namespace Simple_BMI.ViewModels
{
class ViewModel
{
public ViewModel()
{
_Measurement = new Model(180, 6);
}
private Model _Measurement;
public Model Measurement
{
get
{
return _Measurement;
}
}
public void SaveChanges()
{
Debug.Assert(false, String.Format("{0} {1} was updated.", Measurement.Weight, Measurement.Height));
}
}
}
Replace {Binding Path=Model. with {Binding Path=Measurement.. You have the property named differently on ViewModel
See also: Debugging Data Bindings in a WPF or Silverlight Application
Correct your xaml file, using Measurement instead of Model:
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<Label>Weight:</Label>
<TextBox Text="{Binding Measurement.Weight}" Width="136" />
<Button>Update</Button>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label>Height:</Label>
<TextBox Text="{Binding Measurement.Height}" Width="136" />
</StackPanel>
</StackPanel>
I have created a ListBox with custom ListBoxItem Template, had bound everything and it worked. When I was still working on my project. I runned the program and that ListBox weren't showing items anymore. Here is my code:
This is ListBox which doesn't show any item:
<ListBox
x:Name="LB_SongList"
HorizontalAlignment="Left"
Height="498"
Margin="0,30,0,0"
VerticalAlignment="Top"
Width="319"
Background="{x:Null}"
BorderBrush="{x:Null}"
Drop="LB_SongList_Drop"
PreviewMouseMove="LB_SongList_PreviewMouseMove"
PreviewMouseDoubleClick="LB_SongList_PreviewMouseDoubleClick"
SelectionChanged="LB_SongList_SelectionChanged"
AllowDrop="True"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Visible"
SelectionMode="Multiple"
ItemTemplate="{DynamicResource SongTemplate}"/>
Here is my ListBoxItem Template:
<DataTemplate x:Key="SongTemplate">
<Grid Width="302" Height="35">
<Label Content="{Binding SongName}" HorizontalAlignment="Stretch" Margin="4" VerticalAlignment="Stretch" Width="Auto" Height="Auto" Padding="2,0,0,0" VerticalContentAlignment="Center"/>
<Border BorderBrush="#B26A6A6A" BorderThickness="4" HorizontalAlignment="Stretch" Height="35" VerticalAlignment="Top" Width="Auto">
<Border.Style>
<Style>
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}"
Value="False">
<Setter
Property="Border.Visibility"
Value="Collapsed"
/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
</Grid>
</DataTemplate>
And the code:
public partial class MenuHolder : UserControl
{
private List<string> List_SongList;
public List<string> L_SongList
{
get { return List_SongList; }
set
{
_File.ListToObservableCollection_Song(value, O_SongList);
List_SongList = value;
}
}
public List<string> L_PlayLists
{
get
{
Settings.Default.Save();
return Settings.Default.L_PlayLists;
}
set
{
Settings.Default.L_PlayLists = value;
Settings.Default.Save();
}
}
public ObservableCollection<Song> O_SongList = new ObservableCollection<Song>();
public ObservableCollection<string> O_PlayList = new ObservableCollection<string>();
public MenuHolder()
{
InitializeComponent();
LB_SongList.ItemsSource = O_SongList;
LB_PlayList.ItemsSource = O_PlayList;
List<string> temp = new List<string>();
temp.Add("TestSong");
L_SongList = temp;
}
}
public class Song : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
string _songName;
string _songLengh;
public Song(){}
public Song(String _name){ SongName = _name; }
public Song(String _name, String _lengh) { SongName = _name; SongLengh = _lengh; }
public string SongName
{
get { return _songName; }
set { _songName = value; RaisePropertyChanged("SongName"); }
}
public string SongLengh
{
get { return _songLengh; }
set { _songLengh = value; RaisePropertyChanged("SongLengh"); }
}
private void RaisePropertyChanged(string propertyName)
{
// take a copy to prevent thread issues
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And the ListToObservableCollection_Song
public void ListToObservableCollection_Song(List<string> _list, ObservableCollection<Music_Player.Menus.MainMenuObjects.Song> _collection)
{
_collection.Clear();
foreach (string _path in _list)
{
_collection.Add(new Menus.MainMenuObjects.Song(GetSongNameFromPath(_path)));
}
}
The worst thing is that it was working before and in the old version of my program it is still working. Still thinking why it is doing that.
How can be done a 2D of 10x10 selectable elements each element with text in a windows form?
Is there an easy way of doing this?
I need to select some elements in an orderable way (indexed) in the grid in order to send new positions to my robot. I mean: 1st: go to first selected element of the grid (labeled as 1 when selected)
2nd: go to the second selected element of the grid (labeled as 2 when selected) ... and so on...
The grid would look like this:
(source: xmswiki.com)
I am trying to avoid putting 100 checkboxes close to each other...
Posting this as an answer because the OP asked for it:
<Window x:Class="MiscSamples.GridRobot"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="GridRobot" Height="500" Width="600">
<DockPanel>
<DockPanel DockPanel.Dock="Top">
<TextBlock Text="Size:" Margin="2" DockPanel.Dock="Left"/>
<TextBox Text="{Binding Size}" IsReadOnly="True" DockPanel.Dock="Left" Margin="2" Width="50"/>
<Slider Maximum="20" Minimum="2" Value="{Binding Size}"/>
</DockPanel>
<StackPanel DockPanel.Dock="Left" Width="100" Margin="2">
<TextBlock Text="Route:" TextAlignment="Center" FontWeight="Bold"/>
<ItemsControl ItemsSource="{Binding Route}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock TextAlignment="Center">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0:D2},{1:D2}">
<Binding Path="Row"/>
<Binding Path="Column"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<Grid>
<ItemsControl ItemsSource="{Binding Squares}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="{Binding Size}" Columns="{Binding Size}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="DarkGray" BorderThickness="1">
<Button Command="{Binding DataContext.GoToCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding}">
<Button.Template>
<ControlTemplate>
<Border Background="#05FFFFFF">
<Viewbox>
<TextBlock Text="{Binding PathIndex}"
TextAlignment="Center" VerticalAlignment="Center"/>
</Viewbox>
</Border>
</ControlTemplate>
</Button.Template>
</Button>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Canvas>
<!-- I was about to add the Robot Peg here and animate it -->
</Canvas>
</Grid>
</DockPanel>
</Window>
Code Behind:
public partial class GridRobot : Window
{
public GridRobot()
{
InitializeComponent();
DataContext = new GridRobotViewModel();
}
}
View Model:
public class GridRobotViewModel: PropertyChangedBase
{
private int _size;
public int Size
{
get { return _size; }
set
{
_size = value;
OnPropertyChanged("Size");
CreateItems();
}
}
private ObservableCollection<GridItem> _squares;
public ObservableCollection<GridItem> Squares
{
get { return _squares ?? (_squares = new ObservableCollection<GridItem>()); }
}
private ObservableCollection<GridItem> _route;
public ObservableCollection<GridItem> Route
{
get { return _route ?? (_route = new ObservableCollection<GridItem>()); }
}
private void CreateItems()
{
Squares.Clear();
Route.Clear();
for (int i = 0; i < Size; i++)
{
for (int j = 0; j < Size; j++)
{
Squares.Add(new GridItem() {Row = i, Column = j});
}
}
}
private Command<GridItem> _goToCommand;
public Command<GridItem> GoToCommand
{
get { return _goToCommand ?? (_goToCommand = new Command<GridItem>(Goto){IsEnabled = true}); }
}
private void Goto(GridItem item)
{
if (item.PathIndex == null)
{
item.PathIndex = Squares.Max(x => x.PathIndex ?? 0) + 1;
Route.Add(item);
}
}
}
Data Item:
public class GridItem: PropertyChangedBase
{
public int Row { get; set; }
public int Column { get; set; }
private int? _pathIndex;
public int? PathIndex
{
get { return _pathIndex; }
set
{
_pathIndex = value;
OnPropertyChanged("PathIndex");
}
}
}
Support Classes for MVVM:
public class PropertyChangedBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
Application.Current.Dispatcher.BeginInvoke((Action) (() =>
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}));
}
}
public class Command<T>: ICommand
{
public Action<T> Action { get; set; }
public void Execute(object parameter)
{
if (Action != null && parameter is T)
Action((T)parameter);
}
public bool CanExecute(object parameter)
{
return IsEnabled;
}
private bool _isEnabled;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
}
public event EventHandler CanExecuteChanged;
public Command(Action<T> action)
{
Action = action;
}
}
Result:
Just copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself.
You said 10 x 10, but I went a step further and added a Slider to make the grid size customizable.
Clicking on any cell will make it be queued as part of the Route.
Fully Resolution Independent.
I was about to start putting some really nice stuff on it (animations, robot movement represented by an ellipse, Lines for the Path, etc).
Forget winforms, it's useless.