I've got a WPF Grid and would like to move rows up or down depending on the user's input. This is what I've tried so far (an example for when the user decides to move an element up):
RowDefinition currentRow = fieldsGrid.RowDefinitions[currentIndex];
fieldsGrid.RowDefinitions.Remove(currentRow);
fieldsGrid.RowDefinitions.Insert(currentIndex - 1, currentRow);
Am I doing something wrong? As the UI remains the same using this approach.
This would be the WPF approach to what you're screenshot looks like:
<Window x:Class="WpfApplication4.Window9"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window9" Height="300" Width="500">
<ItemsControl ItemsSource="{Binding Columns}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter"/>
</DataTemplate.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="25"/>
<ColumnDefinition Width="25"/>
</Grid.ColumnDefinitions>
<!-- This is your Key image, I used a rectangle instead, you can change it -->
<Rectangle Fill="Yellow" Visibility="{Binding IsPrimaryKey, Converter={StaticResource BoolToVisConverter}}" Margin="2"/>
<CheckBox IsChecked="{Binding IsSelected}" Grid.Column="1"/>
<TextBlock Text="{Binding Name}" Grid.Column="2"/>
<ComboBox ItemsSource="{Binding SortOrders}" SelectedItem="{Binding SortOrder}" Grid.Column="3" Margin="2"/>
<Button Content="Up" Grid.Column="4" Margin="2"
Command="{Binding DataContext.MoveUpCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=ItemsControl}}"
CommandParameter="{Binding}"/>
<Button Content="Down" Grid.Column="5" Margin="2"
Command="{Binding DataContext.MoveDownCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=ItemsControl}}"
CommandParameter="{Binding}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
Code Behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using InduraClientCommon.MVVM;
using System.Collections.ObjectModel;
namespace WpfApplication4
{
public partial class Window9 : Window
{
public Window9()
{
InitializeComponent();
var vm = new ColumnListViewModel();
vm.Columns.Add(new ColumnViewModel() { IsPrimaryKey = true, Name = "Customer ID", SortOrder = SortOrder.Ascending });
vm.Columns.Add(new ColumnViewModel() {Name = "Customer Name", SortOrder = SortOrder.Descending});
vm.Columns.Add(new ColumnViewModel() {Name = "Customer Age", SortOrder = SortOrder.Unsorted});
DataContext = vm;
}
}
}
ViewModel:
public class ColumnListViewModel: ViewModelBase
{
private ObservableCollection<ColumnViewModel> _columns;
public ObservableCollection<ColumnViewModel> Columns
{
get { return _columns ?? (_columns = new ObservableCollection<ColumnViewModel>()); }
}
private DelegateCommand<ColumnViewModel> _moveUpCommand;
public DelegateCommand<ColumnViewModel> MoveUpCommand
{
get { return _moveUpCommand ?? (_moveUpCommand = new DelegateCommand<ColumnViewModel>(MoveUp, x => Columns.IndexOf(x) > 0)); }
}
private DelegateCommand<ColumnViewModel> _moveDownCommand;
public DelegateCommand<ColumnViewModel> MoveDownCommand
{
get { return _moveDownCommand ?? (_moveDownCommand = new DelegateCommand<ColumnViewModel>(MoveDown, x => Columns.IndexOf(x) < Columns.Count)); }
}
private void MoveUp(ColumnViewModel item)
{
var index = Columns.IndexOf(item);
Columns.Move(index, index - 1);
MoveUpCommand.RaiseCanExecuteChanged();
MoveDownCommand.RaiseCanExecuteChanged();
}
private void MoveDown(ColumnViewModel item)
{
var index = Columns.IndexOf(item);
Columns.Move(index, index + 1);
MoveUpCommand.RaiseCanExecuteChanged();
MoveDownCommand.RaiseCanExecuteChanged();
}
}
public class ColumnViewModel: ViewModelBase
{
private bool _isPrimaryKey;
public bool IsPrimaryKey
{
get { return _isPrimaryKey; }
set
{
_isPrimaryKey = value;
NotifyPropertyChange(() => IsPrimaryKey);
}
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
NotifyPropertyChange(() => IsSelected);
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyPropertyChange(() => Name);
}
}
private List<SortOrder> _sortOrders;
public List<SortOrder> SortOrders
{
get { return _sortOrders ?? (_sortOrders = Enum.GetValues(typeof(SortOrder)).OfType<SortOrder>().ToList()); }
}
private SortOrder _sortOrder;
public SortOrder SortOrder
{
get { return _sortOrder; }
set
{
_sortOrder = value;
NotifyPropertyChange(() => SortOrder);
}
}
}
public enum SortOrder {Unsorted, Ascending, Descending}
}
This is what it looks like in my screen:
As you can see in the above example, I am in no way manipulating or creating UI elements in code, because it's actually not necessary. Whenever you need to interact with the pieces of information displayed in the screen, you interact with the ViewModels and not the View. This is the clear separation of concerns between UI and application logic WPF makes possible, which is utterly absent in other frameworks. Please consider this approach the de-facto default when doing any kind o N-element UIs in WPF.
Edit:
Advantages of this approach versus the classic one:
No need to manipulate complex WPF classes (I.E UI elements) in your code in order to show / get data from screen (just simple, simple
properties and INotifyPropertyChanged)
Scales better (UI can be anything as long as it honors the ViewModel properties, you could change the ComboBox to a rotating 3d
pink elephant with a Sort order in each foot.
No need to navigate the visual tree to find elements located God knows where.
No need to foreach anything. Just a simple Select that converts your data (from whatever data source you obtained it) to the
ViewModel list.
Bottom line: WPF is much simpler and nicer than anything else currently in existence, if you use the WPF approach.
Here is a quick example of using an ItemsControl to do what you are wanting:
ViewModel
public class ListBoxViewModel
{
private static readonly List<string> sortList = new List<string>() { "Unsorted", "Sorted" };
public List<string> SortList { get { return sortList; } }
public ObservableCollection<ItemDetail> ItemDetails { get; set; }
#region Up Command
ICommand upCommand;
public ICommand UpCommand
{
get
{
if (upCommand == null)
{
upCommand = new RelayCommand(UpExecute);
}
return upCommand;
}
}
private void UpExecute(object param)
{
var id = param as ItemDetail;
if (id != null)
{
var curIndex = ItemDetails.IndexOf(id);
if (curIndex > 0)
ItemDetails.Move(curIndex, curIndex - 1);
}
}
#endregion Up Command
#region Down Command
ICommand downCommand;
public ICommand DownCommand
{
get
{
if (downCommand == null)
{
downCommand = new RelayCommand(DownExecute);
}
return downCommand;
}
}
private void DownExecute(object param)
{
var id = param as ItemDetail;
if (id != null)
{
var curIndex = ItemDetails.IndexOf(id);
if (curIndex < ItemDetails.Count-1)
ItemDetails.Move(curIndex, curIndex + 1);
}
}
#endregion Down Command
public ListBoxViewModel()
{
ItemDetails = new ObservableCollection<ItemDetail>()
{
new ItemDetail() { IsSelected = false, ItemName = "Customer Id", SortOrder = "Unsorted" },
new ItemDetail() { IsSelected = true, ItemName = "Customer Name", SortOrder = "Sorted" },
new ItemDetail() { IsSelected = false, ItemName = "Customer Age", SortOrder = "Unsorted" }
};
}
}
ItemDetail Class (Made up by me to make things easier)
public class ItemDetail
{
public bool IsSelected { get; set; }
public string ItemName { get; set; }
public string SortOrder { get; set; }
}
XAML
<UserControl.Resources>
<DataTemplate DataType="{x:Type vm:ItemDetail}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="CheckBoxGroup" />
<ColumnDefinition SharedSizeGroup="ItemNameGroup" />
<ColumnDefinition SharedSizeGroup="SortGroup" />
<ColumnDefinition Width="20" />
<ColumnDefinition SharedSizeGroup="UpArrowGroup" />
<ColumnDefinition SharedSizeGroup="DownArrowGroup" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0" IsChecked="{Binding IsSelected}" VerticalAlignment="Center" />
<Label Grid.Column="1" Content="{Binding ItemName}" />
<ComboBox Grid.Column="2" ItemsSource="{Binding DataContext.SortList, RelativeSource={RelativeSource AncestorType={x:Type views:ListBoxExample}}}" SelectedItem="{Binding SortOrder}" />
<Button Grid.Column="4" Command="{Binding DataContext.UpCommand, RelativeSource={RelativeSource AncestorType={x:Type views:ListBoxExample}}}" CommandParameter="{Binding}">
<Image Source="..\images\up.png" Height="10" />
</Button>
<Button Grid.Column="5" Command="{Binding DataContext.DownCommand, RelativeSource={RelativeSource AncestorType={x:Type views:ListBoxExample}}}" CommandParameter="{Binding}">
<Image Source="..\images\down.png" Height="10" />
</Button>
</Grid>
</DataTemplate>
</UserControl.Resources>
<Grid Grid.IsSharedSizeScope="True">
<ItemsControl ItemsSource="{Binding ItemDetails}" />
</Grid>
And finally the results:
And after pressing the down arrow on the first item:
Hope this helps.
You are changing the order of the RowDefinitions, which is not what you want. You want to change the assignment of elements to rows, which is determined by the Grid.Row attached property
I would put all controls that belong to each row in a container (one per row) and then use Grid.SetRow to change the containers around. See how to change the grid row of the control from code behind in wpf.
Related
I have a questionnaire application and I would like to display the objects (questions + answers in my case) one at a time. Meaning that I would like the user to be displayed the first question, answer it, then click a button and get the 2nd question (in the same window), click another button / or the same button and get the 3rd question and so on. At the moment, I am having difficulties trying to bind the 2nd question to my button by using only XAML. Is it possible to do this without using code behind? If so please do give me an idea. Thank you in anticipation.
This is my ViewModel:
namespace TestAppMVVM.ViewModel
{
public class TestViewModel
{
public ObservableCollection<Test> BeginnerTests
{
get;
set;
}
public Test CurrentQuestion
{
get;
set;
}
public Test NextQuestion
{
get;
set;
}
public Test CurrentAnswer
{
get;
set;
}
public Test NextAnswer
{
get;
set;
}
public void LoadBeginner()
{
ObservableCollection<Test> tests = new ObservableCollection<Test>();
tests.Add(new Test { Index = 1, Question = "1.What is the capital of England ?", FirstAnswer = "Paris", SecondAnswer = "London", ThirdAnswer = "Berlin" });
tests.Add(new Test { Index = 2, Question = "2.What is the capital of France ?", FirstAnswer = "Paris", SecondAnswer = "London", ThirdAnswer = "Berlin" });
tests.Add(new Test { Index = 3, Question = "3.What is the capital of Germany ?", FirstAnswer = "Paris", SecondAnswer = "London", ThirdAnswer = "Berlin" });
BeginnerTests = tests;
CurrentQuestion = BeginnerTests[0];
CurrentAnswer = BeginnerTests[0];
NextQuestion = BeginnerTests[1];
NextAnswer = BeginnerTests[1];
}
}
}
And here I paste the View:
<Grid Background="Yellow">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height ="20"/>
<RowDefinition Height ="100"/>
<RowDefinition Height ="auto"/>
<RowDefinition Height ="auto"/>
<RowDefinition Height ="30"/>
<RowDefinition Height ="*"/>
<RowDefinition Height ="20"/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="1" Grid.Row="1" FontSize="25" Grid.ColumnSpan="3" Text="{Binding CurrentQuestion.Question}"/>
<StackPanel Orientation="Horizontal" Grid.Column="2" Grid.Row="3" Grid.ColumnSpan="5">
<RadioButton FontWeight="Bold" Content="{Binding CurrentAnswer.FirstAnswer}"/>
<RadioButton FontWeight="Bold" Margin="10 0 0 0" Content="{Binding CurrentAnswer.SecondAnswer}"/>
<RadioButton FontWeight="Bold" Margin="10 0 0 0" Content="{Binding CurrentAnswer.ThirdAnswer}"/>
</StackPanel>
<Button FontWeight="Bold" x:Name="nextButton" Content="Next Question" Grid.Column="2" Grid.Row="5"/>
If you want to avoid using the code behind and implement MVVM for this, you will need to implement ICommand.
Put this in your ViewModel:
public ICommand ExecuteCommand
{
get
{
if (_command == null)
{
_command = new RelayCommand(param => this.NextQuestion());
}
return _command;
}
}
public void NextQuestion()
{
//Do Stuff Here
}
Bind your button to it by adding this to your XAML:
Command="{Binding Path=ExecuteCommand}"
And, finally, add a RelayCommand class to your project:
public class RelayCommand : ICommand
{
readonly Action<object> _ActionToExecute;
readonly Predicate<object> _ActionCanExecute;
public RelayCommand(Action<object> inActionToExecute): this(inActionToExecute, null)
{
}
public RelayCommand(Action<object> inActionToExecute, Predicate<object> inActionCanExecute)
{
if (inActionToExecute == null)
throw new ArgumentNullException("execute");
_ActionToExecute = inActionToExecute;
_ActionCanExecute = inActionCanExecute;
}
public bool CanExecute(object parameter)
{
return _ActionCanExecute == null ? true : _ActionCanExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_ActionToExecute(parameter);
}
}
There's a way which is exposing an ICommand in ViewModel named MoveNext
The ICommand interface is here https://msdn.microsoft.com/en-us/library/system.windows.input.icommand(v=vs.110).aspx
class MoveNextCommand : ICommand
{
...... <Your implementation>
}
public class TestViewModel
{
...
private ICommand _moveNextCmd;
public ICommand MoveNextCmd
{
get { return _moveNextCmd ?? (_moveNextCmd = new MoveNextCommand()); }
}
...
}
In XAML
<Button FontWeight="Bold" x:Name="nextButton" Content="Next Question" Grid.Column="2" Grid.Row="5" Command={Binding MoveNextCmd} />
The command's logic is just changing the CurrentQuestion of the ViewModel.
The important thing here is that your ViewModel must implement INotifyPropertyChanged interface and setter of the properties must call OnPropertyChanged, otherwise you won't see any update when changing the property's value.
I have an WPF application using MVVM.
I have a DataGrid bound to an ObservableCollection and a TextBox bound to the DataGrid SelectedItem, so when I click an item in the DataGrid, the TextBox is populated.
I also have a Button using Command and CommandParameter and using RelayCommand to check if the TextBox is empty and then disabling the Button.
That all works great, if I use UpdateSourceTrigger=PropertyChanged. The thing I don't like is because of the binding, if the user changes the text in the TextBox, the DataGrid record is edited. If the user then changes their mind about changing the record, and clicks somewhere else, the record in the DataGrid still shows the edited text.
What I have tried is using Mode=OneWay on the TextBox binding, which works in that it doesn't update the DataGrid record. After the data is saved to the database, I need to manually refresh the DataGrid to show the changes.
The code I have in my code behind is the DataGrid's SelectionChanged event which sets a property on the ViewModel to the selected item.
So in order to show the new changes, I thought adding a call to my GetCategories again after the changes would work. However when the code executes OnPropertyChanged("ReceivedCategories"), my CurrentCategory property becomes null.
My code:
CategoryModel.cs
public class CategoryModel
{
public int CategoryID { get; set; }
public string Description { get; set; }
readonly SalesLinkerDataContext _dbContext = new SalesLinkerDataContext();
public ObservableCollection<CategoryModel> GetCategories()
{
var result = _dbContext.tblSalesCategories.ToList();
List<CategoryModel> categoriesList = result.Select(item => new CategoryModel
{
CategoryID = item.CategoryID,
Description = item.Description.Trim()
}).ToList();
return new ObservableCollection<CategoryModel>(categoriesList);
}
internal bool UpdateCategory(int id, string description)
{
if (_dbContext.tblSalesCategories.Any(x => x.Description == description))
{
MessageBox.Show("A category with the same name already exists.");
return false;
}
try
{
var category = (from a in _dbContext.tblSalesCategories
where a.CategoryID == id
select a).FirstOrDefault();
if (category != null)
{
category.Description = description;
_dbContext.SubmitChanges();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return false;
}
return true;
}
internal bool AddCategory(string description)
{
if (_dbContext.tblSalesCategories.Any(x => x.Description == description))
{
MessageBox.Show("A category with the same name already exists.");
return false;
}
var newCategory = new tblSalesCategory();
newCategory.Description = description;
try
{
_dbContext.tblSalesCategories.InsertOnSubmit(newCategory);
_dbContext.SubmitChanges();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return false;
}
return true;
}
internal bool DeleteCategory(int id)
{
var result = _dbContext.tblSalesCategories.FirstOrDefault(x => x.CategoryID == id);
try
{
if (result != null)
{
_dbContext.tblSalesCategories.DeleteOnSubmit(result);
_dbContext.SubmitChanges();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return false;
}
return true;
}
}
CategoriesViewModel.cs
public class CategoriesViewModel : ViewModelBase, IPageViewModel
{
public CategoryModel CurrentCategory = new CategoryModel();
public ObservableCollection<CategoryModel> Categories = new ObservableCollection<CategoryModel>();
public RelayCommand GetCategoriesRelay;
public RelayCommand UpdateCategoryRelay;
public RelayCommand AddCategoryRelay;
public RelayCommand DeleteCategoryRelay;
#region Get Categories Command
public ICommand GetCategoriesCommand
{
get
{
GetCategoriesRelay = new RelayCommand(p => GetCategories(),
p => CanGetCategories());
return GetCategoriesRelay;
}
}
private bool CanGetCategories()
{
return true;
}
private void GetCategories()
{
Categories = CurrentCategory.GetCategories();
ReceivedCategories = Categories;
}
#endregion
#region Update Category Command
public ICommand UpdateCategoryCommand
{
get
{
UpdateCategoryRelay = new RelayCommand(p => UpdateCategory((string) p),
p => CanUpdateCategory());
return UpdateCategoryRelay;
}
}
public bool CanUpdateCategory()
{
return !String.IsNullOrWhiteSpace(Description);
}
public void UpdateCategory(string description)
{
if (CurrentCategory.UpdateCategory(CurrentCategory.CategoryID, description))
{
GetCategories();
}
}
#endregion
#region Add Category Command
public ICommand AddCategoryCommand
{
get
{
AddCategoryRelay = new RelayCommand(p => AddCategory((string) p),
p => CanAddCategory());
return AddCategoryRelay;
}
}
private bool CanAddCategory()
{
return !String.IsNullOrWhiteSpace(Description);
}
private void AddCategory(string description)
{
if (CurrentCategory.AddCategory(description))
GetCategories();
}
#endregion
#region Delete Category Command
public ICommand DeleteCategoryCommand
{
get
{
DeleteCategoryRelay = new RelayCommand(p => DeleteCategory((int) p),
p => CanDeleteCategory());
return DeleteCategoryRelay;
}
}
private bool CanDeleteCategory()
{
return true;
}
private void DeleteCategory(int id)
{
if (CurrentCategory.DeleteCategory(id))
GetCategories();
}
#endregion
/// <summary>
/// Describes the name that will be used for the menu option
/// </summary>
public string Name
{
get { return "Manage Categories"; }
}
public string Description
{
get
{
return CurrentCategory.Description;
}
set
{
CurrentCategory.Description = value;
OnPropertyChanged("Description");
}
}
public ObservableCollection<CategoryModel> ReceivedCategories
{
get { return Categories; }
set
{
Categories = value;
OnPropertyChanged("ReceivedCategories");
}
}
}
CategoryView.xaml
<UserControl x:Class="SalesLinker.CategoriesView"
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:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="600" Background="White">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding GetCategoriesCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="45"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Margin="20,0,0,0" FontSize="20" HorizontalAlignment="Center" Content="Categories"/>
<DataGrid x:Name="LstCategories" Grid.Column="0" Grid.Row="1" AutoGenerateColumns="false"
ItemsSource="{Binding Path=ReceivedCategories, Mode=TwoWay}" SelectionChanged="Selector_OnSelectionChanged"
HorizontalScrollBarVisibility="Disabled" GridLinesVisibility="None"
CanUserAddRows="False" CanUserDeleteRows="False" CanUserSortColumns="True" Background="White">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=Description}" IsReadOnly="True" Header="Description" Width="300" />
</DataGrid.Columns>
</DataGrid>
<Button Command="{Binding AddCategoryCommand}" Grid.Column="1" Grid.Row="1" VerticalAlignment="Top" Height="50" Width="50" Margin="0,20,0,0" Background="Transparent" BorderThickness="0" BorderBrush="Transparent"
CommandParameter="{Binding ElementName=TbDescription, Path=Text}">
<Image Source="/Images/Plus.png"/>
</Button>
<Button Command="{Binding DeleteCategoryCommand}" Grid.Column="1" Grid.Row="1" VerticalAlignment="Top" Height="50" Width="50" Margin="0,75,0,0" Background="Transparent" BorderThickness="0" BorderBrush="Transparent"
CommandParameter="{Binding SelectedItem.CategoryID, ElementName=LstCategories, Mode=OneWay }">
<Image Source="/Images/Minus.png"/>
</Button>
<Grid Grid.Row="1" Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="75"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Row="0" Grid.Column="0" Content="Description:"/>
<TextBox x:Name="TbDescription" DataContext="CategoryModel" Grid.Row="0"
Grid.Column="1" Width="250" Height="Auto" VerticalAlignment="Center"
HorizontalAlignment="Left" Margin="10,0,0,0"
Text="{Binding SelectedItem.Description, ElementName=LstCategories, Mode=OneWay}"/>
<Button Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left" Margin="10,0,0,0"
Height="20" Width="120" Content="Update Description"
Command="{Binding UpdateCategoryCommand}"
CommandParameter="{Binding ElementName=TbDescription, Path=Text}" />
</Grid>
</Grid>
And I have also just noticed that using Mode=OneWay on the TextBox breaks my CanExecute pieces of code as well.
So all I can think of is either:
Find a way to bind another property to the TextBox as well?
Find a way of using UpdateSourceTrigger=PropertyChanged on the TextBox, but prevent the DataGrid being updated.
Any ideas?
For the null value in the Observable List you have to update the list with a copy of it and an Dispatcher.Invoke otherwise the gui will crash or show null I had a similar problem on update a observable list in a thread.
So you should write your changes in a list like:
//Your Copy List
ObservableCollection<CategoryModel> _ReceivedCategories;
//Your Execute command for the Gui
public void onUpdateExecuted(object parameter){
Dispatcher.Invoke(new Action(() => ReceivedCategories = new ObservableCollection <CategoryModel> (_ReceivedCategories));
}
//Your Undo Command if the Inputs are not ok
public void onUndoExecuted(object parameter){
Dispatcher.Invoke(new Action(() => _ReceivedCategories = new ObservableCollection <CategoryModel> (ReceivedCategories));
}
//Your Command on every input which is set
public void onInputExecuted(object parameter){
_ReceivedCategories.add(Your Object);
}
And you can add an update Command for the Main List so you first update the Copy List with the values and after an Command set the Copy Collection in the Main Collection
Hope that helps
I couldn't get the above suggestion by SeeuD1 to work, but have have figured out what the problem was.
I have The DataGrid's ItemsSource bound to ReceivedCategories and in the code behind, the SelectionChanged event to use to bind to the TextBox.
After the database save, I call my GetCategories() again to refresh the data.
So when ReceivedCategories gets the data again, the DataGrid's ItemsSource is updated, but not before the SelectionChange event fires trying to set my CurrentCategory to the SelectedItem. However there is no longer anything selected in the DataGrid (index is -1), so assignment fails and sets CurrentCategory to null.
Fix is to simply only assign CurrentCategory to the SelectedItem if something is selected.
Old code:
private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var viewmodel = (CategoriesViewModel)DataContext;
viewmodel.CurrentCategory = LstCategories.SelectedItems.Cast<CategoryModel>().FirstOrDefault();
}
Fixed:
private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var viewmodel = (CategoriesViewModel)DataContext;
if (LstCategories.SelectedIndex > -1)
viewmodel.CurrentCategory = LstCategories.SelectedItems.Cast<CategoryModel>().FirstOrDefault();
}
Thanks for the suggestions.
Here is the complete Sample code
Using MVVM pattern My requirement is to have a ListView where
If user Taps inside ListView on checkBox Storyboard Animation should play for True False and the ListView binded value should be updated in database. For true the tick should pop up with animation for false the tick should become invisible with animation. Solved as per #Elvis Xia answer
If user taps on ListviewItem Navigate to new page with value
Blueprint
Now I went with Usercontrol creation for the datatemplate. Here I want to identify both events separately user clicking on checkbox or clicking on Item separately. Using ICommand I am creating two Delegates that gets binded to two transparent button which relays tapped event. Dependency of creating transparent buttons and creating delgates while binding them made me think surely there must a better way in which I can utilize MVVM for these events without any code behind.
UserControl XAML
<Button Background="LightBlue" BorderBrush="White" BorderThickness="4" Command="{x:Bind sampleItem.itemTapped}" CommandParameter="{Binding}" HorizontalContentAlignment="Stretch">
<Grid Margin="20">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Margin="20" HorizontalAlignment="Center" Text="{x:Bind sampleItem.sampleText}" FontSize="30"/>
<Image Grid.Column="1" Height="60" Width="60" Source="ms-appx:///Assets/check_off.png" HorizontalAlignment="Right"/>
<Image x:Name="image" Grid.Column="1" Height="60" Width="60" Source="ms-appx:///Assets/check_on.png" HorizontalAlignment="Right" Visibility="Collapsed" RenderTransformOrigin="0.5,0.5">
<Image.RenderTransform>
<CompositeTransform/>
</Image.RenderTransform>
</Image>
<Button x:Name="btnFav" Grid.Column="1" Height="60" Width="60" HorizontalAlignment="Right" Background="Transparent" Command="{x:Bind sampleItem.favTapped}" CommandParameter="{Binding}">
<Interactivity:Interaction.Behaviors>
<!--<Core:EventTriggerBehavior EventName="Tapped">
<Core:InvokeCommandAction Command="{Binding favTapped}" />
</Core:EventTriggerBehavior>-->
<Core:DataTriggerBehavior Binding="{Binding isFav}" Value="true">
<Media:ControlStoryboardAction Storyboard="{StaticResource StoryboardCheckOn}"/>
</Core:DataTriggerBehavior>
<Core:DataTriggerBehavior Binding="{Binding isFav}" Value="false">
<Media:ControlStoryboardAction Storyboard="{StaticResource StoryboardCheckOff}"/>
</Core:DataTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</Button>
</Grid>
</Button>
UserControl XAML codeBehind
MainPageModel sampleItem { get { return this.DataContext as MainPageModel; } }
public MainPageUserControl()
{
this.InitializeComponent();
this.DataContextChanged += (s, e) => this.Bindings.Update();
}
Viewmodel Code
public async Task GetData()
{
for (int i = 0; i < 10; i++)
{
if (i == 3)
sampleList.Add(new MainPageModel { sampleText = "Selected", isFav = true, favTapped= new DelegateCommand<MainPageModel>(this.OnFavTapped), itemTapped= new DelegateCommand<MainPageModel>(this.OnItemTapped)});
else
sampleList.Add(new MainPageModel { sampleText = "UnSelected"+i.ToString(), isFav = null, favTapped = new DelegateCommand<MainPageModel>(this.OnFavTapped), itemTapped = new DelegateCommand<MainPageModel>(this.OnItemTapped) });
}
}
private void OnFavTapped(MainPageModel arg)
{
if (arg.isFav == null) arg.isFav = true;
else
arg.isFav = !arg.isFav;
}
private void OnItemTapped(MainPageModel arg)
{
System.Diagnostics.Debug.WriteLine("Button Value: "+arg.sampleText);
System.Diagnostics.Debug.WriteLine("Selected Item Value: "+selectedItem.sampleText);
}
MainPage Xaml
<Grid Grid.Row="1">
<ListView ItemsSource="{x:Bind ViewModel.sampleList}" IsItemClickEnabled="True" SelectedItem="{Binding ViewModel.selectedItem,Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<userControls:MainPageUserControl/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
There must be a better way to achieve the desired result using code behind.
public class ViewMOdel()
{
public ViewModel()
{
favTapped= new DelegateCommand<MainPageModel>(this.OnFavTapped)
itemTapped= new DelegateCommand<MainPageModel>(this.OnItemTapped)
}
public async Task GetData()
{
for (int i = 0; i < 10; i++)
{
if (i == 3)
sampleList.Add(new MainPageModel { sampleText = "Selected", isFav = true});
else
sampleList.Add(new MainPageModel { sampleText = "UnSelected"+i.ToString(), isFav = null});
}
}
private void OnFavTapped(MainPageModel arg)
{
if (arg.isFav == null) arg.isFav = true;
else
arg.isFav = !arg.isFav;
}
private void OnItemTapped(MainPageModel arg)
{
System.Diagnostics.Debug.WriteLine("Button Value: "+arg.sampleText);
System.Diagnostics.Debug.WriteLine("Selected Item Value: "+selectedItem.sampleText);
}
}
Binding should be like this
<Button x:Name="btnFav" Grid.Column="1" Height="60" Width="60" HorizontalAlignment="Right" Background="Transparent" Command="{Binding ElementName=UserControl, Path=Tag.favTapped}" CommandParameter="{Binding}"/>
Update
<ListView ItemsSource="{x:Bind ViewModel.sampleList}" x:Name="Listview"IsItemClickEnabled="True" SelectedItem="{Binding ViewModel.selectedItem,Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<userControls:MainPageUserControl Tag="{Binding DataContext,ElementName=Listview}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button x:Name="btnFav" Grid.Column="1" Height="60" Width="60" HorizontalAlignment="Right" Background="Transparent" Command="{Binding ElementName=UserControl, Path=Tag.favTapped}" CommandParameter="{Binding}"/>
Update2 using EventTriggerBehavior
favTapped = new DelegateCommand<RoutedEventArgs>(this.OnFavTapped);
private void OnFavTapped(RoutedEventArgs arg)
{
var item = (( arg.OriginalSource )as Button).DataContext as MainPageModel
}
<Button n x:Name="btnFav" Grid.Column="1" Height="60" Width="60" HorizontalAlignment="Right" Background="Transparent" >
<interact:Interaction.Behaviors>
<interactcore:EventTriggerBehavior EventName="Click" >
<interactcore:InvokeCommandAction Command="{Binding ElementName=usercontrol, Path=Tag.favTapped}" />
</interactcore:EventTriggerBehavior>
</interact:Interaction.Behaviors>
</Button>
The DataContext of every item in your project is an instance of MainPageModel class. So the favTapped command should be added to MainPageModel class. And it is a command, so favTapped should be an instance of a new class,which implements ICommand interface.
And if you don't want the animation to show at the page's first load, you can set isFav to bool?. And when the page first loads, set the value of isFav to null, thus it won't trigger the animation action.
Below are the Codes snippets and Demo Link:
ViewModelCommands.cs:
public class ViewModelCommands : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
//if it's a tapped event
if (parameter is TappedRoutedEventArgs)
{
var tappedEvent = (TappedRoutedEventArgs)parameter;
var gridSource = (Grid)tappedEvent.OriginalSource;
var dataContext = (MainPageModel)gridSource.DataContext;
//if tick is true then set to false, or the opposite.
if (dataContext.isFav == null)
{
dataContext.isFav = true;
} else
{
dataContext.isFav = !dataContext.isFav;
}
}
}
}
MainPageModel.cs:
public class MainPageModel:BindableBase
{
public MainPageModel() {
favTapped = new ViewModelCommands();
}
public ViewModelCommands favTapped { get; set; }
private string _sampleText;
public string sampleText
{
get
{
return this._sampleText;
}
set
{
Set(ref _sampleText, value);
}
}
private bool? _isFav;
public bool? isFav
{
get
{
return this._isFav;
}
set
{
Set(ref _isFav, value);
}
}
}
Here is the complete Demo:Demo Project
Update:
When using DelegateCommand, you can add the command Property to MainPageModel.cs and since the DataContext of the items are MainPageModel instances. You can use this.isFav to change the clicked item's value of isFav.
Here are the codes of MainPageModel.cs:
public class MainPageModel : BindableBase
{
private DelegateCommand _favTapped;
public DelegateCommand favTapped
{
get
{
if (_favTapped == null)
{
_favTapped = new DelegateCommand(() =>
{
//Here implements the check on or off logic
this.CheckOnOff();
}, () => true);
}
return _favTapped;
}
set { _favTapped = value; }
}
private void CheckOnOff()
{
if (this.isFav == null)
{
this.isFav = true;
}
else
{
this.isFav = !this.isFav;
}
}
private string _sampleText;
public string sampleText
{
get
{
return this._sampleText;
}
set
{
Set(ref _sampleText, value);
}
}
private bool? _isFav;
public bool? isFav
{
get
{
return this._isFav;
}
set
{
Set(ref _isFav, value);
}
}
}
For Listview item selected
You can use ListView.ItemClick Event. But you should also set IsItemClickEnabled="True",otherwise the event handler won't be fired.
For The subitem of Listview tapped
You can use Tapped Event of userControl.
Here are the Xaml codes, that shows how to register the above two events:
<Grid Grid.Row="1">
<ListView IsItemClickEnabled="True" ItemClick="ListView_ItemClick_1" ItemsSource="{x:Bind ViewModel.sampleList}">
<ListView.ItemTemplate>
<DataTemplate>
<userControls:MainPageUserControl Tapped="MainPageUserControl_Tapped"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
I'm facing a problem in my WPF project at the moment. At this moment I have a Viewmodel which has a Manager (to communicate with the repo).
internal class TicketViewModel
{
private TicketManager mgr;
public IEnumerable<Ticket> List { get; set; }
public TicketViewModel()
{
mgr = new TicketManager();
List = mgr.GetTickets();
}
}
I've managed to bind this list to the Listbox in my MainWindow. The next step is that I need to add an extra ticket to the list and also pass this through the manager. The problem is I need two parameters from some Controls in the MainWindow. From MVVM perspective I need to use bound Commands on e.g. a Button to communicate with the viewmodel as my viewmodel can't/may not access controls from the window. Is using parameterized Commands the way to go here?
The next problem is that the Listbox won't update I guess. This is the code:
<ListBox x:Name="listboxTix" BorderThickness="0" ItemsSource="{Binding List}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Bisque" Background="Beige" BorderThickness="2">
<StackPanel Width="250">
<TextBlock Text="{Binding TicketNumber}" />
<TextBlock Text="{Binding Text}" />
<TextBlock Text="{Binding State}" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I found that using a CompareableCollection is the way to go here, but then I still have to read all the Tickets again after adding a new Ticket.
Thanks in advance,
Hicy
okay here is the code.
Lets say you have three textboxes on MainWindow(since you have three Textblocks.) so Your MainWindow.xaml looks like
<Window.DataContext>
<local:MyViewModel/>--set's your viewModel
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="250*"/>
<RowDefinition Height="90"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" x:Name="listboxTix" BorderThickness="0" ItemsSource="{Binding List}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Bisque" Background="Beige" BorderThickness="2">
<StackPanel Width="250">
<TextBlock Text="{Binding TicketNumber}" />
<TextBlock Text="{Binding Text}" />
<TextBlock Text="{Binding State}" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox x:Name="TicketNumber" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Path=Text}" VerticalAlignment="Top" Width="120"/>
<TextBox x:Name="Text" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Path=State}" />
<TextBox x:Name="State" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Path=TicketNumber}" />
<Button Content="Button" Command="{Binding Path=MainCommand}" Grid.Row="2"/>
</Grid>
and I am assuming that you have some class called class Ticket which contain these three members
Class Ticket
{
public int TicketNumber { get; set; }
public string Text { get; set; }
public string State { get; set; }
}
Now in class TicketManager we fill it with some dummy data
class TicketManager
{
ObservableCollection<Ticket> tl = new ObservableCollection<Ticket>();
internal ObservableCollection<Ticket> GetTickets()
{
tl.Add(new Ticket() { State = "State1", Text = "Text1", TicketNumber = 1 });
tl.Add(new Ticket() { State = "State2", Text = "Text2", TicketNumber = 2 });
tl.Add(new Ticket() { State = "State3", Text = "Text3", TicketNumber = 3 });
return tl;
}
}
and in your Mainwindow ViewModel lets call it MyViewModel.cs we add
class MyViewModel:INotifyPropertyChanged
{
private TicketManager mgr;
public ObservableCollection<Ticket> List { get; set; }
private string text;
private string state;
private int ticketNumber;
private readonly DelegateCommand<object> MyButtonCommand;
public Class1()
{
mgr = new TicketManager();
List = mgr.GetTickets();
MyButtonCommand = new DelegateCommand<object>((s) => { AddListToGrid(text, state, ticketNumber); }, (s) => { return !string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(state); });
}
private void AddListToGrid(string text, string state, int ticketNumber)
{
List.Add(new Ticket() {Text=text,State=state,TicketNumber=ticketNumber });
}
public DelegateCommand<object> MainCommand
{
get
{
return MyButtonCommand;
}
}
public string Text
{
get
{
return text;
}
set
{
text = value;
OnPropertyChanged("Text");
MyButtonCommand.RaiseCanExecuteChanged();
}
}
public string State
{
get
{
return state;
}
set
{
state = value;
OnPropertyChanged("State");
MyButtonCommand.RaiseCanExecuteChanged();
}
}
public int TicketNumber
{
get
{
return ticketNumber;
}
set
{
ticketNumber = value;
OnPropertyChanged("TicketNumber");
MyButtonCommand.RaiseCanExecuteChanged();
}
}
private void OnPropertyChanged(string p)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
public event PropertyChangedEventHandler PropertyChanged;
}
You can Modify the code in anyway you want
This ViewModel implements fewthings which are very important from MVVM point of view
1) INotifyPropertyChanged
2) WPF Delegate Command
P.S:The code is tested and it runs as expected
Don't get hung up on MVVM it is simply a separation of data from a view, and models are shared between the two with a majority of the business logic (on a shared component) should be performed on the VM; it is not a religion just a three tiered data system. IMHO
If your button needs to do an operation, have it make a call, most likely in the code behind, to a method on the VM which handles the business logic, updates the list with the new item and notifies the manager.
I would bind the list in question to an ObservableCollection which can notify upon insert/delete of an item.
This is my first post on StackOverflow and also my first Question.
I had created a UserControl in WPF with the MVVM Pattern (called Block).
This UserControl should be used in another UserControl, as DataTemplate of a ListBox (called Sector).
The Block Control should be used as standalone and as DataTemplate for different UserControls.
If I set the DataContext in the View of Block, the standalone Version works great, but not as part of the Sector View.
If I don´t set the DataContext in the View of Block, it works in Sector but don’t standalone.
My question is, is it the only way to leave the DataContext in the View of Block and set it in the View I used the Control or the ViewModel?
Here is my Code:
Model of Block:
public class BlockModel : ModelBase
{
#region private Variables
string blockName = "Block";
string blockContent = "00000";
#endregion private Variables
#region Properties
public string BlockName
{
get { return blockName; }
set { blockName = value; NotifyPropertyChange("BlockName "); }
}
public string BlockContent
{
get { return blockContent; }
set { blockContent = value; NotifyPropertyChange("BlockContent"); }
}
#endregion Properties
#region ctor
public BlockModel () { }
#endregion ctor
}
ViewModel of Block:
public class BlockViewModel : ViewModelBase
{
#region private Variables
string charToFill = "0";
DelegateCommand fillWithChar;
BlockModel dataModel = new BlockModel();
#endregion private Variables
#region Properties
public Models. BlockModel DataModel
{
get { return dataModel; }
set { dataModel = value; }
}
public string BlockContent
{
get { return dataModel. BlockContent; }
set
{
if (dataModel. BlockContent != value)
{
dataModel. BlockContent = value;
NotifyPropertyChange ("BlockContent");
}
}
}
public string BlockName
{
get { return dataModel. BlockName; }
set
{
if (dataModel. BlockName != value)
{
dataModel. BlockName = value;
NotifyPropertyChange("BlockName");
}
}
}
public string CharToFill
{
get { return charToFill; }
set
{
if (charToFill != value)
{
charToFill = value;
NotifyPropertyChange ("CharToFill");
}
}
}
public ICommand FillWithChar
{
get
{
if (fillWithChar == null)
fillWithChar = new DelegateCommand(FillText);
return fillWithChar;
}
}
#endregion Properties
#region ctor
public BlockViewModel()
{
}
#endregion ctor
#region Methods
private void FillText()
{
CodingBlockContent = CodingBlockContent.PadLeft(32, charToFill[0]);
}
#endregion Methods
}
View of Block:
<UserControl x:Class="…./Block"
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:…/Controls"
mc:Ignorable="d" d:DesignWidth="460" d:DesignHeight="44">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="124" />
<ColumnDefinition Width="301" />
<ColumnDefinition Width="30"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding BlockName}"
HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="14" Margin="2,6"/>
<ComboBox FontFamily="Consolas" Grid.Row="00" Grid.Column="1"
Text="{Binding BlockContent, Mode=TwoWay}"
Tag="{Binding BlockName}" Name="cbxBlock" FontSize="14"
HorizontalAlignment="Left" VerticalAlignment="Center" Width="290" Margin="1,4,0,5" IsEditable="True" Height="26">
<ComboBoxItem Content=""></ComboBoxItem>
<ComboBoxItem Content="0000000000000"></ComboBoxItem>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Horizontal">
<TextBox Margin="10,2,0,2" Text="0" Width="24" FontSize="14" Name="tbxChar" MaxLength="1"></TextBox>
<TextBlock Margin="10,2,0,2" Text="auffüllen" VerticalAlignment="Center" FontSize="14"></TextBlock>
<Button Margin="10,2,0,2" Content="Ausführen" Width="100" Command="{Binding FillWithChar}"></Button>
</StackPanel>
</ComboBox>
<TextBlock Grid.Row="0" Width="30" Grid.Column="2" Text="{Binding CodingBlockContent.Length}"
HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="14" Margin="2,6,0,6" Grid.ColumnSpan="2"/>
</Grid>
Model of Sector:
public class SectorModel
{
#region private Variables
int blockAmount = 4;
string sectorNumber = "Sektor";
int blockBegin = 0;
bool isSectorInUse = false;
#endregion private Variables
#region Properties
public string SectorNumber
{
get { return sectorNumber; }
set { sectorNumber = value;}
}
public int BlockBegin
{
get { return blockBegin; }
set
{
blockBegin = value;
}
}
public bool IsSectorInUse
{
get { return isSectorInUse; }
set { isSectorInUse = value;}
}
public int BlockAmount
{
get { return blockAmount; }
set
{
blockAmount = value;;
}
}
#endregion Properties
public SectorModel()
{
}
}
ViewModel of Sector:
public class SectorViewModel : ViewModelBase
{
#region private Variables
SectorModel dataModel = new SectorModel();
ObservableCollection<BlockViewModel> sectorBlocks = new ObservableCollection<BlockViewModel>();
#endregion private Variables
#region Properties
public SectorModel DataModel
{
get { return dataModel; }
set { dataModel = value; }
}
public ObservableCollection<BlockViewModel> SectorBlocks
{
get { return sectorBlocks; }
set { sectorBlocks = value; NotifyPropertyChange ("SectorBlocks"); }
}
public string SectorNumber
{
get { return "Sektor " + DataModel.SectorNumber; }
set { DataModel.SectorNumber = value; NotifyPropertyChange ("SectorNumber"); }
}
public int BlockBegin
{
get { return DataModel.BlockBegin; }
set
{
DataModel.BlockBegin = value;
SetBlocks();
OnPropertyChanged("BlockBegin");
}
}
public bool IsSectorInUse
{
get { return DataModel.IsSectorInUse; }
set { DataModel.IsSectorInUse = value; NotifyPropertyChange ("IsSectorInUse"); }
}
public int BlockAmount
{
get { return DataModel.BlockAmount; }
set
{
DataModel.BlockAmount = value;
SetBlocks();
NotifyPropertyChange ("CodingBlockAmount");
}
}
#endregion Properties
void SetBlocks()
{
while (SectorBlocks.Count != BlockAmount)
{
SectorBlocks.Add(new BlockViewModel());
}
int begin = BlockBegin;
foreach (BlockViewModel block in SectorBlocks)
{
block.CodingBlockName = "Block " + begin.ToString().PadLeft(2, '0');
block++;
}
}
public SectorViewModel()
{
SetBlocks();
}
}
View of Sector:
<UserControl xmlns:Views="clr-namespace:…/RFIDControls" x:Class="…/Sector"
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="473">
<Border BorderBrush="Black" BorderThickness="0,0,0,1">
<Expander Name="expMain" Margin="0,0,4,0">
<Expander.Header>
<Grid Name="grdHeader">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="300" />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Height="24" Text="{Binding SectorNumber}" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="14" FontWeight="Bold" Panel.ZIndex="98"/>
<CheckBox Grid.Column="2" VerticalAlignment="Center" IsChecked="{Binding IsSectorInUse}"></CheckBox>
</Grid>
</Expander.Header>
<Grid>
<ListBox ItemsSource="{Binding SectorBlocks}" Name="listBox">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type Views:BlockViewModel}">
<Views:Block/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Expander>
</Border>
Thank you.
With kind regards from Germany
Dominik
PS: I hope my English is not so bad as I suppose
This is a common problem for new users of WPF. You have two possible solutions. When using MVVM, you shouldn't need to set the DataContext of the UserControl anywhere. Instead, you can simply add a DataTemplate to your App.xaml to make the pairing:
<DataTemplate DataType="{x:Type ViewModels:BlockViewModel}">
<Views:BlockView />
</DataTemplate>
In this way, whenever you show an instance of your BlockViewModel, the Framework will display the related BlockView instead (like you have done in your SectorView class).
The alternative option is to not use MVVM for the UserControl (use Bindable DependencyPropertys instead), to not set its DataContext internally and to use RelativeSource Bindings on the elements inside instead:
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding DataContext.BlockName,
RelativeSource={RelativeSource AncestorType={
x:Type YourXmlNamespacePrefix:BlockView}}}" HorizontalAlignment="Left"
VerticalAlignment="Center" FontSize="14" Margin="2,6"/>
Using this method, the RelativeSource Binding will look at whatever object is currently set as the DataContext.
Sounds like Block act as an control, which seems participate in other usercontrol or part of Template, in my opinion, mvvm doesn't work in here, you should use Dependency property instead.
Forgive my poor english.