WPF Control losing focus when clicking on a tab - wpf

On a tabcontrol I have several tabpages, on one of the tabs there is a textbox in the content.
This textbox is content bound with a simple Path=PropertyName and UpdateSourceTrigger=LostFocus. The reason I am using LostFocus is I trap the Lost focus event of the Textbox and possibly reformat the text. This is a "time" textbox and if they enter "0900", I want to reformat to "09:00". This part works great when I press the tab key to move to the next control, but if I type "0900" then press one of the other tabs, I hit the lost focus and re-format the value in the textbox, BUT the bind never gets called to update my object. When I come back to the tab, the value is blanked out (or reset to the original value on the object)
Any ideas why textbox does not trigger the Binding update when changing tab page?
Note: this also happens with a regular textbox that does wire to the lost focus event. It seems to have something to do with click on the tab.
[[Added Code ]]
More notes:
1. I am dynamically creating the tabs and controls on the tab (not sure if that has something to do with it or not)
2. I am using the Prism libraries
MainWindow Xaml
<Window.Resources>
<DataTemplate DataType="{x:Type ctrls:myTextBoxDef}">
<Grid Width="300">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="28" />
</Grid.RowDefinitions>
<TextBlock Grid.Column="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding LabelText}" />
<TextBox Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding DocValue,
Mode=TwoWay,
ValidatesOnDataErrors=True,
UpdateSourceTrigger=LostFocus}"
/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<TabControl HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
IsTabStop="False"
ItemsSource="{Binding Tabs, Mode=OneWay}"
SelectedItem="{Binding SelectedTab,
Mode=TwoWay,
NotifyOnSourceUpdated=True}"
>
<TabControl.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Margin="18,14,22,0"
Text="{Binding HeaderText}" />
</Grid>
</DataTemplate>
</TabControl.ItemTemplate>
<!-- Content -->
<TabControl.ContentTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<AdornerDecorator Grid.Column="0">
<ItemsControl Grid.Column="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsTabStop="False"
ItemsSource="{Binding Controls,
Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Grid.Column="0"
Margin="10,5,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</AdornerDecorator>
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
Main Window Code Behind
public partial class MainWindow : Window
{
private DataContextObject obj = new DataContextObject();
public MainWindow()
{
InitializeComponent();
myTextBoxDef txt1 = new myTextBoxDef(obj, "Textbox 1", "TAB1TextBox1");
myTextBoxDef txt1b = new myTextBoxDef(obj, "Textbox 1 value", "TAB1TextBox1");
myTextBoxDef txt2 = new myTextBoxDef(obj, "Textbox 2", "TAB1TextBox2");
myTextBoxDef txt2b = new myTextBoxDef(obj, "Textbox 2 value", "TAB1TextBox2");
obj.Tabs.Add(new myTabDef("Tab 1", new ObservableCollection<myTextBoxDef>() { txt1, txt2 }));
obj.Tabs.Add(new myTabDef("Tab 2", new ObservableCollection<myTextBoxDef>() { txt1b, txt2b }));
obj.SelectedTab = obj.Tabs[0];
this.DataContext = obj;
}
}
Supporting objects
public class DataContextObject : NotificationObject
{
List<myTabDef> _tabs = new List<myTabDef>();
public List<myTabDef> Tabs
{
get
{
return _tabs;
}
}
private myTabDef _item;
public myTabDef SelectedTab
{
get
{ return _item; }
set
{
_item = value;
this.RaisePropertyChanged("SelectedItem");
}
}
private string _txt1 = "";
public string TAB1TextBox1
{
get { return _txt1; }
set
{
_txt1 = value;
this.RaisePropertyChanged("TAB1TextBox1");
}
}
private string _txt2 = "";
public string TAB1TextBox2
{
get { return _txt2; }
set
{
_txt2 = value;
this.RaisePropertyChanged("TAB1TextBox2");
}
}
private string _txt3 = "";
public string TAB2TextBox1
{
get { return _txt3; }
set
{
_txt3 = value;
this.RaisePropertyChanged("TAB2TextBox1");
}
}
}
public class myTabDef
{
public myTabDef(string tabText, ObservableCollection<myTextBoxDef> controls)
{
HeaderText = tabText;
_left = controls;
}
public string HeaderText { get; set; }
private ObservableCollection<myTextBoxDef> _left = new ObservableCollection<myTextBoxDef>();
public ObservableCollection<myTextBoxDef> Controls
{
get
{
return _left;
}
}
}
public class myTextBoxDef : NotificationObject
{
public myTextBoxDef(NotificationObject bound, string label, string bindingPath)
{
LabelText = label;
Path = bindingPath;
BoundObject = bound;
BoundObject.PropertyChanged += BoundObject_PropertyChanged;
}
public string LabelText
{
get;
set;
}
public NotificationObject BoundObject
{
get;
set;
}
public string DocValue
{
get
{
return PropInfo.GetValue(BoundObject, null) as string;
}
set
{
PropInfo.SetValue(BoundObject, value, null);
}
}
protected virtual void BoundObject_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName.Equals(Path))
{
this.RaisePropertyChanged("DocValue");
}
}
public string Path
{
get;
set;
}
private PropertyInfo pi = null;
protected PropertyInfo PropInfo
{
get
{
if (pi == null && BoundObject != null && !string.IsNullOrEmpty(Path))
{
PropertyInfo[] properties = BoundObject.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
pi = properties.Where((prop) => string.Compare(prop.Name, Path, true) == 0).FirstOrDefault();
}
return pi;
}
}
}

We have found a solution. I came cross this set of postings
https://groups.google.com/forum/#!topic/wpf-disciples/HKUU61A5l74
They talk about a control called TabControlEx. Towards the bottom (5th from the bottom) you will see a posting by Sacha Barber that has a zip file with an example.
It solved all our problems we were having.
here is also another link where the code for the Class is posted
http://updatecontrols.codeplex.com/discussions/214434

Related

How can I DataBind a textbox from the parent window with values from the child with MVVM?

I just took over a project from another programmer who is no longer here. It was created using the MVVM Pattern (using the MVVM Light toolkit). I am new to MVVM and have been trying to learn the basics fast. Currently I am having trouble getting a selected value from a Child Window back to the Parent Window.
From another post on SO I learned that I should use the same ViewModel for both the parent and the child so I think I have the basics right. However I have not been able to get the selected values back to the parent. Below is a sample set of code similar to the production code.
My ViewModel for both pages is here
public class MainViewModel : ViewModelBase
{
private Vendor selectedVendor = null;
List<Vendor> vendors;
public MainViewModel()
{
OpenVendorWindowCommand = new RelayCommand(VendorSelect);
VendorSelectedCommand = new RelayCommand(VendorSelected);
LoadVendors();
}
public ICommand OpenVendorWindowCommand { get; private set; }
public ICommand VendorSelectedCommand { get; private set; }
void VendorSelect()
{
Messenger.Default.Send(new NotificationMessage("SelectVendor"));
}
public Vendor SelectedVendor
{
get { return selectedVendor; }
set
{
if (selectedVendor != value)
{
selectedVendor = value;
RaisePropertyChanged();
}
}
}
void VendorSelected()
{
Console.WriteLine(SelectedVendor.VendorName);
}
public List<Vendor> Vendors
{
get
{
return vendors;
}
set
{
if (vendors != value)
{
vendors = value;
RaisePropertyChanged();
}
}
}
private void LoadVendors()
{
DataTable dt = new DataTable();
dt = Vendor.GetVendors();
Vendors = new List<Vendor>();
foreach (DataRow row in dt.Rows)
{
Vendors.Add(new Vendor()
{
VendorID = Convert.ToInt32(row["VendorID"]),
VendorCode = Convert.ToString(row["VendorCode"]),
VendorName = Convert.ToString(row["VendorName"])
});
}
}
}
I am at the point that the Child Window opens and I am able to select a vendor from a ListBox. After the selection I press a button (VendorSelectedCommand) and it is at that point I want the textbox on the Parent Window to be filled with the SelectedVendor.VendorName value.
This is the XAML from my Child Window
<StackPanel VerticalAlignment="Center">
<ListBox
Height="200"
Margin="5"
HorizontalAlignment="Stretch"
Background="GhostWhite"
ItemsSource="{Binding Vendors}"
SelectedItem="{Binding Path=SelectedVendor, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="3">
<StackPanel Margin="15">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="175" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
FontWeight="SemiBold"
Foreground="Black"
Text="{Binding VendorName}" />
<TextBlock
Grid.Column="1"
FontWeight="SemiBold"
Foreground="Black">
<Run Text=" (" />
<Run Text="{Binding VendorCode}" />
<Run Text=") " />
</TextBlock>
</Grid>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Command="{Binding VendorSelectedCommand}" Content="Send Vendor Back" />
</StackPanel>
And lastly this is the XAML for the Parent Window with what I think is the correct binding
<StackPanel VerticalAlignment="Center">
<TextBox Margin="10" Text="{Binding SelectedVendor.VendorName}" />
<Button
Margin="10"
Command="{Binding OpenVendorWindowCommand}"
Content="Select Vendor" />
</StackPanel>
I have tried every possible combination of Binding Syntax that I can think of and have tried multiple different ways in the code behind to catch and bind it but have not been able to get it right. What is missing from my ViewModel to make this work?
Edit For clarity (and in response to a comment) I am adding the DataContext, which I had in the Constructor of the Views.
public partial class VendorView : Window
{
private MainViewModel _vm = null;
public VendorView()
{
InitializeComponent();
_vm = new MainViewModel();
DataContext = _vm;
}
}
Edit #2 I am opening the second page with this. This is very simple sample app with only two pages so I didn't want to get bogged down with navigation until I have a better handle on Binding.
private void NotificationMessageReceived(NotificationMessage msg)
{
if (msg.Notification == "SelectVendor")
{
var vendorView = new VendorView();
vendorView.ShowDialog();
}
}

Binding usercontrol inside itemcontrol

I have 2 tasks.
Add a single usercontrol to a parent window.
Add a collection of a usercontrol to a parent window.
I have problem to fulfill task 2 in relation to the data binding and command binding.
if someone knows how to do task 2, please add some code.
This is my implementation for both tasks, in case someone want to fix it.. :
I have a usercontrol called "Book" that contains 3 textblocks and a button.
The userControl has dependecyProperty of my book model and for the button command.
Book.xaml
<UserControl x:Name="MyBookControl"
<Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Controls:BookControl}}, Path=TheBook}">
<Label Grid.Row="0">Title</Label>
<Label Grid.Row="1">Author</Label>
<Label Grid.Row="2">Description</Label>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Title}"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Author}"/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Description}"/>
<Button Grid.Row="3" Command="{Binding
SomeCommand,ElementName=MyBookControl}" Content="Save" />
</Grid>
Book.xaml.cs
public partial class BookControl : UserControl
{
public BookControl()
{
InitializeComponent();
}
public BookModel TheBook
{
get { return (BookModel)GetValue(TheBookProperty); }
set { SetValue(TheBookProperty, value); }
}
public static DependencyProperty TheBookProperty = DependencyProperty.Register("TheBook", typeof(BookModel), typeof(BookControl));
public ICommand SomeCommand
{
get { return (ICommand)GetValue(SomeCommandProperty); }
set { SetValue(SomeCommandProperty, value); }
}
public static readonly DependencyProperty SomeCommandProperty =
DependencyProperty.Register("SomeCommand", typeof(ICommand), typeof(BookControl), new UIPropertyMetadata(null));
}
BookModel.cs
public class BookModel
{
public string Title { get; set; }
public string Author { get; set; }
public string Description { get; set; }
}
In order to complete task 1 I created a window:
BookWindow
<Window
DataContext="{Binding Source={StaticResource Locator}, Path=Main}"
>
<StackPanel>
<Controls:BookControl TheBook="{Binding Book}" SomeCommand="{Binding
SaveCommand}" />
</StackPanel>
BookViewModel.cs
public BookModel Book { get; set; }
public MainViewModel()
{
Book = new BookModel{Title = "A Book", Author = "Some Author",
Description = "Its a really good book!"};
}
private ActionCommand _SaveCommand;
public ICommand SaveCommand
{
get
{
if (_SaveCommand == null)
{
_SaveCommand = new ActionCommand(OnSaveCommand, CanSaveCommand);
}
return _SaveCommand;
}
}
protected virtual void OnSaveCommand()
{
MessageBox.Show("save clicked");
}
protected virtual bool CanSaveCommand()
{
return true;
}
Great, Task 1 Completed
https://onedrive.live.com/redir?resid=3A8F69A0FB413FA4!116&authkey=!AHiyrfEnBr2a-rM&v=3&ithint=photo%2cpng
Now, trying to complete task 2:
ContainerWindow:
<Window
DataContext="{Binding Source={StaticResource Locator}, Path=Container}"
>
<StackPanel>
<ItemsControl ItemsSource="{Binding Books}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Controls:BookControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
ContainerViewModel.cs :
private ObservableCollection<BookModel> books;
public ObservableCollection<BookModel> Books
{
get
{
if (books == null)
{
// Not yet created.
// Create it.
books = new ObservableCollection<BookModel>();
}
return books;
}
}
public ContainerViewModel()
{
BookModel book1 = new BookModel { Title = "A Book 2", Author = "Some Author", Description = "Its a really good book!" };
BookModel book2 = new BookModel { Title = "A Book 3", Author = "Some Author", Description = "Its a really good book!" };
Books.Add(book1);
Books.Add(book2);
}
The Binding fail, the button "save" stops respoding.
https://onedrive.live.com/redir?resid=3A8F69A0FB413FA4!121&authkey=!AKnyQk6Ge_9QHug&v=3&ithint=photo%2cpng
So, what is going on ? why binding fail, why the button "save" is not functioning ?
You're not setting your DependencyProperties in the list example.
<DataTemplate>
<Controls:BookControl />
</DataTemplate>
Look at how you did it in your non-list version.
<Controls:BookControl TheBook="{Binding Book}" SomeCommand="{Binding
SaveCommand}" />
That being said, you don't need the DependencyProperties at all, the UserControl will inherit the DataContext for each 'Book' in the list of books as the ItemsControl creates them. You just need to not set the DataContext on the grid.
Then your button could just bind to the BookViewModel command property.
<Button Grid.Row="3" Command="{Binding SaveCommand}" Content="Save" />
If your concern is not knowing what is available for the inherited DataContext, you could do this to get design time support.
d:DataContext="{d:DesignInstance Type=local:BookViewModel,
IsDesignTimeCreatable=False}"
Just make sure that the following is defined somewhere in the file, it usually is by default.
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Update
So I missed the second issue, should have actually fired up Visual Studio. The issue is that your command is in the MainViewModel.cs. That said, our UserControl has inherited the DataContext of each Book object. The short of it is that the button is looking for the command inside of the Book object.
I'm going to assume that since you have a save command that you will be editing the Book object. So let's take this chance to go ahead and make a ViewModel. I'm going to move the save command to there, so that save is always available off of a BookViewModel. There could be good reasons to have the save command somewhere else, but for simplicity's sake, we'll put it in the ViewModel.
Also, I'm not sure if you have INotifyPropertyChanged implemented anywhere, as your MainViewModel and ContainerViewModel don't show that one is used. If you don't, I'd highly recommend you take a step back and look into an implementation or an MVVM framework for your ViewModels.
BookViewModel.cs
public class BookViewModel
{
private readonly BookModel book;
public BookViewModel(BookModel book)
{
this.book = book;
SaveCommand = new ActionCommand(OnSaveCommand, CanSaveCommand);
}
public ICommand SaveCommand { get; private set; }
public string Title
{
get { return book.Title; }
set { book.Title = value; }
}
public string Author
{
get { return book.Author; }
set { book.Author = value; }
}
public string Description
{
get { return book.Description; }
set { book.Description = value; }
}
protected virtual void OnSaveCommand()
{
MessageBox.Show("Save clicked for the book '" + Title + "'.");
}
protected virtual bool CanSaveCommand()
{
return true;
}
}
That is a very basic example of what you would probably want to do. I wanted to keep it simple to not take away from the example, you will probably want to at least do some null checking.
With the above, you shouldn't have to change your UserControl any, I had to add the row and column definitions, but I ended up with the following:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0">Title</Label>
<TextBlock Grid.Row="0"
Grid.Column="1"
Text="{Binding Title}" />
<Label Grid.Row="1" Grid.Column="0">Author</Label>
<TextBlock Grid.Row="1"
Grid.Column="1"
Text="{Binding Author}" />
<Label Grid.Row="2" Grid.Column="0">Description</Label>
<TextBlock Grid.Row="2"
Grid.Column="1"
Text="{Binding Description}" />
<Button Grid.Row="3"
Grid.Column="0"
Command="{Binding SaveCommand}"
Content="Save" />
</Grid>
Hopefully you noticed that our BookViewModel's constructor accepts a book, so that means that we need to change our ContainerViewModel to house the proper collection and create them correctly.
public class ContainerViewModel
{
private ObservableCollection<BookViewModel> books;
public ContainerViewModel()
{
Books.Add(
new BookViewModel(new BookModel
{
Title = "A Book 2",
Author = "Some Author",
Description = "Its a really good book!"
}));
Books.Add(
new BookViewModel(new BookModel
{
Title = "A Book 3",
Author = "Some Author",
Description = "Its a really good book!"
}));
}
public ObservableCollection<BookViewModel> Books
{
get
{
if (books == null)
{
// Not yet created.
// Create it.
books = new ObservableCollection<BookViewModel>();
}
return books;
}
}
}
All that and your ItemsControl can simply be as follows:
<ItemsControl ItemsSource="{Binding Path=Books}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:MyBookControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Drop into empty collection with gong-wpf

I am using the wpf component https://github.com/punker76/gong-wpf-dragdrop
in order to do drag and drop, but I cannot seem to be able to drop in a empty collection. If I initialize the collection with one element, it works.
I also created my own drop handler to see what happens, but it is never called for the empty collection. Is there any way of enabling drop to an empty collection?
Sample xaml:
<UserControl.Resources>
<wf:MyDefaultDropHandler x:Key="myDND" />
<HierarchicalDataTemplate ItemsSource="{Binding Instructions}" DataType="{x:Type wf:WhileBlock}">
<TextBlock Text="While" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type wf:InstructionsList}">
<ItemsControl ItemsSource="{Binding}"
dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.UseDefaultDragAdorner="True"
dd:DragDrop.DropHandler="{StaticResource myDND}" />
</DataTemplate>
<DataTemplate DataType="{x:Type wf:IfElseBlock}">
<StackPanel>
<TextBlock Text="If" />
<ContentControl Content="{Binding IfInstructions}" />
<TextBlock Text="Else" />
<ContentControl Content="{Binding ElseInstructions}" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Border Grid.Column="0">
<ContentControl Content="{Binding AvailableComponents}" />
</Border>
<Border Grid.Column="1"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.DropHandler="{StaticResource myDND}">
<ContentControl Content="{Binding Instructions}" />
</Border>
</Grid>
My view models. If I uncomment the line //Instructions.Add(new IfElseBlock()); drop works as expected
public abstract class AInstruction
{
}
public abstract class ACondition
{
}
public class InstructionsList : ObservableCollection<AInstruction>
{
}
public class WhileBlock : AInstruction
{
private readonly InstructionsList _instructions = new InstructionsList();
public ACondition ExecuteIf { get; set; }
public InstructionsList Instructions { get { return _instructions; } }
}
public class IfElseBlock : AInstruction
{
private readonly InstructionsList _ifInstructions = new InstructionsList();
private readonly InstructionsList _elseInstructions = new InstructionsList();
public ACondition Condition { get; set; }
public InstructionsList IfInstructions { get { return _ifInstructions; } }
public InstructionsList ElseInstructions { get { return _elseInstructions; } }
}
public class Script
{
private readonly InstructionsList _instructions = new InstructionsList();
private readonly InstructionsList _availableComponents = new InstructionsList();
public Script()
{
AvailableComponents.Add(new IfElseBlock());
AvailableComponents.Add(new IfElseBlock());
AvailableComponents.Add(new IfElseBlock());
AvailableComponents.Add(new WhileBlock());
AvailableComponents.Add(new WhileBlock());
AvailableComponents.Add(new WhileBlock());
//Instructions.Add(new IfElseBlock());
}
public InstructionsList Instructions { get { return _instructions; } }
public InstructionsList AvailableComponents { get { return _availableComponents; } }
}
my handler, just to do some debugging
public class MyDefaultDropHandler : DefaultDropHandler
{
public override void DragOver(IDropInfo dropInfo)
{
Debug.WriteLine("DragOver " + dropInfo.TargetItem);
base.DragOver(dropInfo);
}
public override void Drop(IDropInfo dropInfo)
{
Debug.WriteLine("Drop " + dropInfo.TargetItem);
base.Drop(dropInfo);
}
}
It seems there is no hit target because of the transparent background and the size of the control.
I just added Padding="1" MinHeight="10" Background="White" to my ItemsControl and now drop works for empty collections.
In my sample xaml it would look like this:
<ItemsControl ItemsSource="{Binding}" Padding="1" BorderThickness="5,0,0,0"
MinHeight="10"
Background="White"
dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.UseDefaultDragAdorner="True"
dd:DragDrop.DropHandler="{StaticResource myDND}" />

WPF Changing Datacontexts and views in same window

I am new to WPF am and porting an application from VC++ 6.0/MFC to c#/WPF (VS2013). Most of my windows development has been in VC++/MFC. I am trying to stick to the MVVM pattern and am writing a few proof of concept apps to get my feet wet. I am having one sticking point so far.
When my app starts up it will present a tree view of customers and bills. I have that working well using a simple hierarchical data template with each level binding to my local data type (view model). What I want to have happen is when a bill is selected (right now I have a button to press on the bill template) I want the treeview to be replaced by a detail view of the bill (I don't want a dialog to pop up).
The Xaml for this is:
<DockPanel>
<TreeView x:Name="trvGroups" ItemsSource="{Binding LBGroups}" VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.VirtualizationMode="Recycling">
<TreeView.ItemContainerStyle>
<!--
This Style binds a TreeViewItem to a LBtreeViewItemViewModel
-->
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type local:GroupViewModel}"
ItemsSource="{Binding Children}"
>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding GroupName}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate
DataType="{x:Type local:BillViewModel}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding BillName}" />
<Button Command="{Binding Path=BillEditCommand}">Edit</Button>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</DockPanel>
Right now I have more questions than anything. Should I define each view as user controls and put them in window.resources? Do I use data templates? I assume I would change the data context to point to the detail bill view model. What is the best way to do this?
My goal, to adhere to MVVM as I understand it, is to have nothing in the code behind (or as little as possible).
I'm looking more for pointers to get me started along the right path as I research. I getting a little befuddled at the moment.
Thanks in advance.
I'll Show you a plain Master Details Scenario where you can choose models in your TreeView and Edit Them.
CS :
public partial class MainWindow : Window , INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
private ICommand onEditBillCommand;
public ICommand OnEditBillCommand
{
get
{
if (onEditBillCommand == null)
onEditBillCommand = new RelayCommand<Bill>
(
bill => { CurrentBill = bill; }
);
return onEditBillCommand;
}
}
private Bill currectBill;
public Bill CurrentBill
{
get { return currectBill; }
set
{
currectBill = value;
PropertyChanged(this, new PropertyChangedEventArgs("CurrentBill"));
}
}
public List<Customer> Customers
{
get
{
List<Customer> customers = new List<Customer>();
for (int i = 0; i < 5; i++)
{
customers.Add(CreateMockCustomer(i));
}
return customers;
}
}
private Customer CreateMockCustomer(int g )
{
Customer c = new Customer();
c.Name = "John (" + g + ")" ;
for (int i = 0; i < 3; i++)
{
c.Bills.Add(CreateMockBill());
}
return c;
}
private Bill CreateMockBill()
{
Bill b = new Bill();
b.Price = 55.5;
b.BoughtOnDate = DateTime.Now.Date;
return b;
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
public class Customer : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
private ObservableCollection<Bill> bills;
public ObservableCollection<Bill> Bills
{
get
{
if (bills == null)
{
bills = new ObservableCollection<Bill>();
}
return bills;
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
public class Bill : INotifyPropertyChanged
{
private double price;
public double Price
{
get { return price; }
set
{
price = value;
PropertyChanged(this, new PropertyChangedEventArgs("Price"));
}
}
private DateTime boughtOnDate;
public DateTime BoughtOnDate
{
get { return boughtOnDate; }
set
{
boughtOnDate = value;
PropertyChanged(this, new PropertyChangedEventArgs("BoughtOnDate"));
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
public interface IRelayCommand : ICommand
{
void RaiseCanExecuteChanged();
}
public class RelayCommand<T> : IRelayCommand
{
private Predicate<T> _canExecute;
private Action<T> _execute;
public RelayCommand(Action<T> execute, Predicate<T> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
private void Execute(T parameter)
{
_execute(parameter);
}
private bool CanExecute(T parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public bool CanExecute(object parameter)
{
return parameter == null ? false : CanExecute((T)parameter);
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
var temp = Volatile.Read(ref CanExecuteChanged);
if (temp != null)
temp(this, new EventArgs());
}
}
XAML :
<Window>
<Window.Resources>
<HierarchicalDataTemplate x:Key="customerTemplate" DataType="{x:Type local:Customer}" ItemsSource="{Binding Bills}">
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Price}" />
<TextBlock Text="{Binding BoughtOnDate}" Grid.Column="1" />
<Button Content="Edit" Grid.Column="2"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=DataContext.OnEditBillCommand}"
CommandParameter="{Binding}"/>
</Grid>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<TextBlock Text="{Binding Name}" FontFamily="Arial" FontSize="16" FontWeight="Bold" />
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="0.05*"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TreeView ItemsSource="{Binding Customers}" ItemTemplate="{StaticResource customerTemplate}">
</TreeView>
<Grid Grid.Column="2" DataContext="{Binding CurrentBill, Mode=OneWay}" Background="AliceBlue">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Text="{Binding Price, Mode=TwoWay}" Margin="50"/>
<TextBox Text="{Binding BoughtOnDate, Mode=TwoWay}" Grid.Row="1" Margin="50"/>
</Grid>
</Grid>

WPF simple binding problem

Trying to understand this binding process of the WPF.
See the code at the bottom.
In my "viewmodel", see the code at the bottom, i have an observable collection that is populating the listview with the items. Thats the one that contains a path called symbol to set the selected index in the combobox. Now my problem is that i need to populate the combobox from another list before its added to the listview (some default values).
Since i just started with WPF i thought that perhaps you can use 2 different ObservableCollections in the same class to achieve this but that didn't work. So how can i populate the datatemplate with the default values before they are bound/added to the listview?
This is what i use to populate the listview, see the viewmodelcontacts at the bottom.
I also tried to add another class with a new observablecollection that i could use in my combobox, but i didn't get that to work either.
The data that should be populated comes from a XML file located as a resource in my app.
Another question, is it possible to add commands to images? or are commands only available from controls that inherit from the button_base class? I wanted to use an image next to an element and when the user clicked on that image they would remove the element.
From the answer below, is it possible without adding a button since i don't want the button feeling (for instance when hovering and clicking)*
Window.xaml:
<r:RibbonWindow x:Class="Onyxia_KD.Windows.ContactWorkspace"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:r="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"
Title="Contact card" ResizeMode="NoResize" Height="600" Width="600"
Background="White">
<r:RibbonWindow.Resources>
<DataTemplate x:Key="cardDetailFieldTemplate">
<TextBox Text="{Binding Path=Field}" MinWidth="150"></TextBox>
</DataTemplate>
<DataTemplate x:Key="cardDetailValueTemplate">
<TextBox Text="{Binding Path=Value}" MinWidth="150"></TextBox>
</DataTemplate>
<DataTemplate x:Key="cardDetailSymbolTemplate">
<!-- Here is the problem. Populating this with some default values for each entry before the selectedIndex is bound from the datasource -->
<ComboBox SelectedIndex="{Binding Path=Symbol}" DataContext="_vmSettings" ItemsSource="{Binding Symbols}" Grid.Column="1" Margin="0,0,10,0" VerticalAlignment="Center" HorizontalAlignment="Center">
<ListViewItem Padding="0,3,0,3" Content="{Binding Path=Value}" />
</ComboBox>
</DataTemplate>
<DataTemplate x:Key="cardDetailCategoryTemplate">
<ComboBox SelectedIndex="{Binding Path=Category}" Grid.Column="1" Margin="0,0,10,0" VerticalAlignment="Center" HorizontalAlignment="Center">
<!--same as the combobox above but categories instead of symbols-->
</ComboBox>
</DataTemplate>
</r:RibbonWindow.Resources>
...
<ListView ItemsSource="{Binding ContactData}" Foreground="Black" SelectionMode="Single" x:Name="cardDetailList" KeyDown="cardDetailList_KeyDown">
<ListView.View>
<GridView>
<GridViewColumn Header="Category" Width="auto" CellTemplate="{StaticResource cardDetailCategoryTemplate}"></GridViewColumn>
<GridViewColumn Header="Field" Width="auto" CellTemplate="{StaticResource cardDetailFieldTemplate}"></GridViewColumn>
<GridViewColumn Header="Symbol" Width="70" CellTemplate="{StaticResource cardDetailSymbolTemplate}"></GridViewColumn>
<GridViewColumn Header="Value" Width="auto" CellTemplate="{StaticResource cardDetailValueTemplate}"></GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Code behind:
private ViewModelContacts _vm;
public ContactWorkspace()
{
InitializeComponent();
_vm = new ViewModelContacts();
this.DataContext = _vm;
}
private void Run_MouseUp(object sender, MouseButtonEventArgs e)
{
_vm.AddNewDetail();
}
private void Image_MouseUp(object sender, MouseButtonEventArgs e)
{
_vm.AddNewDetail();
}
private void cardDetailList_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Delete)
{
if (MessageBox.Show("Are you sure that you want to delete this?", "Warning!", MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.Yes)
{
_vm.RemoveDetail(cardDetailList.SelectedIndex);
}
}
}
ViewModelContacts:
public ObservableCollection<ContactCardData> ContactData { get; set; }
public ViewModelContacts()
{
ContactData = new ObservableCollection<ContactCardData>();
Populate();
}
private void Populate()
{
ContactData.Add(new ContactCardData("Test", 0, 0, "Value123"));
ContactData.Add(new ContactCardData("Test2", 1, 1, "Value1234"));
ContactData.Add(new ContactCardData("Test3", 2, 2, "Value1235"));
ContactData.Add(new ContactCardData("Test4", 3, 3, "Value12356"));
}
public void UpdateNode()
{
ContactData.ElementAt(0).Value = "Giraff";
}
public void AddNewDetail()
{
ContactData.Add(new ContactCardData());
}
public void RemoveDetail(int position)
{
ContactData.RemoveAt(position);
}
ViewModelContactData:
public class ContactCardData : DependencyObject
{
public int Category
{
get { return (int)GetValue(CategoryProperty); }
set { SetValue(CategoryProperty, value); }
}
// Using a DependencyProperty as the backing store for Category. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CategoryProperty =
DependencyProperty.Register("Category", typeof(int), typeof(ContactCardData), new UIPropertyMetadata(0));
public string Field
{
get { return (string)GetValue(FieldProperty); }
set { SetValue(FieldProperty, value); }
}
// Using a DependencyProperty as the backing store for Field. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FieldProperty =
DependencyProperty.Register("Field", typeof(string), typeof(ContactCardData), new UIPropertyMetadata(""));
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(ContactCardData), new UIPropertyMetadata(""));
public int Symbol
{
get { return (int)GetValue(SymbolProperty); }
set { SetValue(SymbolProperty, value); }
}
// Using a DependencyProperty as the backing store for Symbol. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SymbolProperty =
DependencyProperty.Register("Symbol", typeof(int), typeof(ContactCardData), new UIPropertyMetadata(0));
public ContactCardData()
{
}
public ContactCardData(string field, int category, int symbol, string value)
{
this.Symbol = symbol;
this.Category = category;
this.Field = field;
this.Value = value;
}
}
Define your Window class as follows (explanation will be later):
<Window x:Class="TestCustomTab.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:TestCustomTab="clr-namespace:TestCustomTab" Title="Window1" Height="300" Width="300">
<Window.Resources>
<DataTemplate x:Key="workingTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Name}"/>
<ComboBox Grid.Column="1" SelectedIndex="0" ItemsSource="{Binding Symbols}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Fill="Red" Height="5" Width="5"/>
<TextBlock Grid.Column="1" Text="{Binding}" />
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</DataTemplate>
<DataTemplate x:Key="personalTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Name}"/>
<ComboBox Grid.Column="1" SelectedIndex="0" ItemsSource="{Binding Symbols}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Fill="Green" Height="5" Width="5"/>
<TextBlock Grid.Column="1" Text="{Binding}" />
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</DataTemplate>
<TestCustomTab:ContactDataByTypeTemplateSelector x:Key="contactDataByTypeTemplateSelector"/>
</Window.Resources>
<Grid x:Name="grid">
<ListView ItemsSource="{Binding ContactDataCollection, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TestCustomTab:Window1}}}">
<ListView.View>
<GridView>
<GridViewColumn Header="Column" Width="200" CellTemplateSelector="{StaticResource contactDataByTypeTemplateSelector}">
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
Lets assume that your ContactData looks as follows:
public class ContactData : INotifyPropertyChanged
{
private string _name;
private int _homePhone;
private int _mobilePhone;
private string _value;
private ContactDataType _dataType;
public ContactData()
{
}
public ContactData(string name, int homePhone, int mobilePhone, string value, ContactDataType dataType)
{
_name = name;
_dataType = dataType;
_value = value;
_mobilePhone = mobilePhone;
_homePhone = homePhone;
}
#region Implementation of INotifyPropertyChanged
public ContactDataType DataType
{
get { return _dataType; }
set
{
if (_dataType == value) return;
_dataType = value;
raiseOnPropertyChanged("DataType");
}
}
public string Name
{
get { return _name; }
set
{
if (_name == value) return;
_name = value;
raiseOnPropertyChanged("Name");
}
}
public int HomePhone
{
get { return _homePhone; }
set
{
if (_homePhone == value) return;
_homePhone = value;
raiseOnPropertyChanged("HomePhone");
}
}
public int MobilePhone
{
get { return _mobilePhone; }
set
{
if (_mobilePhone == value) return;
_mobilePhone = value;
raiseOnPropertyChanged("MobilePhone");
}
}
public string Value
{
get { return _value; }
set
{
if (_value == value) return;
_value = value;
raiseOnPropertyChanged("Value");
raiseOnPropertyChanged("Symbols");
}
}
public ReadOnlyCollection<char> Symbols
{
get
{
return !string.IsNullOrEmpty(_value) ? new ReadOnlyCollection<char>(_value.ToCharArray()) : new ReadOnlyCollection<char>(new List<char>());
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void raiseOnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
It has DataType property of type ContactDataType which is enum:
public enum ContactDataType
{
Working,
Personal
}
In ability to have different DataTemplates for the same entities differentiated by some feature you need to use DataTemplateSelector. The technique is in inheriting from DataTemplateSelector and overriding SelectTemplate method. In our case:
public class ContactDataByTypeTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var contactData = item as ContactData;
var control = container as FrameworkElement;
if (contactData != null & control != null)
switch (contactData.DataType)
{
case ContactDataType.Working:
return control.TryFindResource("workingTemplate") as DataTemplate;
case ContactDataType.Personal:
return control.TryFindResource("personalTemplate") as DataTemplate;
default:
return base.SelectTemplate(item, container);
}
return base.SelectTemplate(item, container);
}
}
Here is Window1 class in code behind:
public partial class Window1 : Window
{
private ObservableCollection<ContactData> _contactDataCollection = new ObservableCollection<ContactData>();
public Window1()
{
ContactDataCollection.Add(new ContactData("test1", 0, 1, "value1", ContactDataType.Working));
ContactDataCollection.Add(new ContactData("test2", 0, 1, "value2", ContactDataType.Working));
ContactDataCollection.Add(new ContactData("test3", 0, 1, "value3", ContactDataType.Working));
ContactDataCollection.Add(new ContactData("test4", 0, 1, "value4", ContactDataType.Personal));
ContactDataCollection.Add(new ContactData("test5", 0, 1, "value5", ContactDataType.Personal));
InitializeComponent();
}
public ObservableCollection<ContactData> ContactDataCollection
{
get { return _contactDataCollection; }
}
}
Now explanation:
We created some class that we need to represent to user (ContactData) and let him to have feature - ContactDataType.
We created 2 DataTemplates in resources (x:Key is important) for ContactDataType.Working and ContactDataType.Personal
We created DataTemplateSelector to have ability switch templates by feature.
In our first GridViewColumn we defined CellTemplateSelector and bind to it our ContactDataByTypeTemplateSelector.
In runtime whenever the collection changes ContactDataByTypeTemplateSelector select to us template based on item feature and we may have any number of templates for any number of defined features.
Notice: change TestCustomTab for your namespace.
For the last question, you can use a button and template it to include an image.

Resources