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}" />
Related
Foreach treeview-item i got an own datagrid. Treeview-items and datagrids are filled by binding.
On textboxes i got a binding to the selected item of the datagrids. But the binding on these textboxes only works with the first datagrid. Every other datagrid doesn't transfer the selecteditem to the textboxes:
Here is the treeview with the datagrid:
<TreeView ItemsSource="{Binding Path=PlaceList}">
<TreeView.ItemTemplate>
<DataTemplate>
<TreeViewItem Header="{Binding Path=Name}">
<DataGrid ItemsSource="{Binding MachinesInPlace, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectionUnit="FullRow"
SelectedItem="{Binding SelectedMachine, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
AutoGenerateColumns="True"
IsSynchronizedWithCurrentItem="True"
SelectionMode="Single">
</DataGrid>
</TreeViewItem>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Here is the textbox:
<TextBox Text="{Binding PlaceList/SelectedMachine.Name, ValidatesOnDataErrors=True}" />
I am working with MvvmLight. My ViewModel holds the PlaceList:
public ObservableCollection<PlaceModel> PlaceList { get; set; } = new ObservableCollection<PlaceModel>();
public ObjectInspectorViewModel()
{
PlaceList = PlaceModel.GetPlaces(BaseResourcePaths.PlacesCsv);
}
That s my place-model:
public class PlaceModel
{
public int Id { get; set; }
public string Name { get; set; } = "_CurrentObjectName";
public string Length { get; set; }
public string Width { get; set; }
public string Height { get; set; }
public ObservableCollection<MachineModel> MachinesInPlace { get; set; }
public MachineModel SelectedMachine { get; set; }
public static ObservableCollection<PlaceModel> GetPlaces(string filepath)
{
[...]
}
}
I tried something out but at last i dont know how to fix the bug. What s the problem? My suggestion is the property ''SelectedMachine'' inside the place-model...
Here is an example-project (with the additional solution of Sebastian Richter). It shows the problems: https://www.file-upload.net/download-12370581/DatagridTreeViewError.zip.html
I'm quiet sure you forget to implement INotifyPropertyChanged in you class PlaceModel. The problem is after you changed the selection, the Property Placemodel.SelectedMachine will be updated but no event will be fired to populate this change in the View.
Because you use MVVM Light you can derive from ObservableObject which already implements this Interface.
So change your PlaceModel to following code:
public class PlaceModel : ObservableObject
{
private MachineModel _selectedMachine;
public int Id { get; set; }
public string Name { get; set; } = "_CurrentObjectName";
public string Length { get; set; }
public string Width { get; set; }
public string Height { get; set; }
public ObservableCollection<MachineModel> MachinesInPlace { get; set; }
public MachineModel SelectedMachine
{
get
{
return _selectedMachine;
}
set
{
// raises Event PropertyChanged after setting value
Set(ref _selectedMachine, value);
}
}
public static ObservableCollection<PlaceModel> GetPlaces(string filepath)
{
[...]
}
Edit:
I guess the binding doesn't know which element to bind to from your ObservableCollection (many to one relation) because you set it as the reference in your TextBox.
So try to remove the SelectedMachine property from the Model and add it back to the ViewModel:
class ViewModel : ViewModelBase
{
...
private MachineModel _selectedMachine;
public MachineModel SelectedMachine
{
get
{
return _selectedMachine;
}
set
{
// raises Event PropertyChanged after setting value
Set(ref _selectedMachine, value);
}
}
...
}
Also change your XAML to following code (I used your example project):
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="2*"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<!-- Row #1 -->
<Grid>
<!-- TreeView und DataGrids-->
<TreeView ItemsSource="{Binding Path=PlaceList}">
<TreeView.ItemTemplate>
<DataTemplate>
<TreeViewItem Header="{Binding Path=Name}">
<DataGrid ItemsSource="{Binding MachinesInPlace, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding DataContext.SelectedMachine, RelativeSource={RelativeSource AncestorType=Window},Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</TreeViewItem>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
<!-- Row #2 -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="2*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Label Grid.Row="0"
Content="ID" />
<!-- Textboxen aktualisieren nur bei Auswahl der ersten Datagrid -->
<TextBox Grid.Column="2"
Grid.Row="0"
Text="{Binding SelectedMachine.Id, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Label Grid.Row="1"
Content="Name" />
<TextBox Grid.Column="2"
Grid.Row="1"
Text="{Binding SelectedMachine.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Grid>
The key was to set the correct DataContext for SelectedItem. For this i used following XAML code:
<DataGrid ItemsSource="{Binding MachinesInPlace, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding DataContext.SelectedMachine, RelativeSource={RelativeSource AncestorType=Window},Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
With this the your example project updates the TextBoxes correctly.
My WCF application supports downloading books from a remote service. The user sends a request to the service to download a book, the service gets the request and downloads the book. in response, it sends the download progress for the requested book.
Each element in the BookContainer is a userControl of Book.xaml and its logic represented by bookviewmodel.cs class.
When the user clicks on each book element in the BookContainer.xaml window, the click command inside the bookviewmodel is raised as expected.
I need the help to implement "DownlaodAllCommand" inside containerviewmodel to raise each DownloadCommand of each book.
How should it be implemented using the mvvm pattern.
BookContainer view :
https://onedrive.live.com/redir?resid=3A8F69A0FB413FA4!124&authkey=!ANdfYAk6f0Vf-8s&v=3&ithint=photo%2cpng
code behind:
BookContainer.xaml:
<Window x:Name="BookContainer"
DataContext="{Binding Source={StaticResource Locator}, Path=ContainerViewModel}">
<DockPanel>
<ItemsControl DockPanel.Dock="Top" ItemsSource="{Binding Books}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate >
<Controls:BookControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button DockPanel.Dock="Bottom" Command="{Binding DownloadAllCommand}" Content="Download All" > </Button>
</DockPanel>
</Window>
ContainerViewModel
public class ContainerViewModel : ViewModelBase
{
private ObservableCollection<BookViewModel> books;
public ObservableCollection<BookViewModel> Books
{
get
{
if (books == null)
{
// Not yet created.
// Create it.
books = new ObservableCollection<BookViewModel>();
}
return books;
}
}
private ActionCommand _DownloadAllCommand;
public ICommand DownloadAllCommand
{
get
{
if (_DownloadAllCommand == null)
{
_DownloadAllCommand = new ActionCommand(OnDownloadAllCommand, CanDownloadAllCommand);
}
return _DownloadAllCommand;
}
}
private void OnDownloadAllCommand()
{
// help !
}
private bool CanDownloadAllCommand()
{
return true;
}
}
Book.xaml
<UserControl
<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.Column="2" Grid.RowSpan="3" Command="{Binding DownloadCommand}" Content="Download" />
<Ellipse Grid.Column="3"
Height="20" Width="20"
Stroke="Black"
StrokeThickness="0.5"
HorizontalAlignment="Center"
Grid.Row="1"
/>
<Controls:PieSlice Grid.Column="3" Grid.Row="1" Stroke="Black" Fill="Black"
Height="20" Width="20"
StartAngle="0" EndAngle="{Binding Percent}"
HorizontalAlignment="Center" />
</UserControl>
BookViewModel
public class BookViewModel : ViewModelBase
{
public delegate void DownloadRequest(string name);
public event DownloadRequest OnDownalodRequest;
public BookViewModel(BookModel model)
{
this.Book = model;
}
private ActionCommand _DownloadCommand;
public ICommand DownloadCommand
{
get
{
if (_DownloadCommand == null)
{
_DownloadCommand = new ActionCommand(OnDownloadCommand, CanDownloadCommand);
}
return _DownloadCommand;
}
}
protected virtual void OnDownloadCommand()
{
if (OnDownalodRequest != null)
{
OnDownalodRequest.Invoke(this.Author);
}
}
}
BookModel
public class BookModel
{
public string Title { get; set; }
public string Author { get; set; }
public string Description { get; set; }
public Status Status { get; set; }
}
Insid the ContainerViewModel, method OnSaveAllCommand():
Books.ToList().Foreach(book =>
{
if(book.SaveCommand.CanExecute() == false) return;
book.SaveCommand.Execute()
}
);
but take in account that this is a bad practice to use a viewmodel from another viewmodel, try to perform viewmodels communnication on a model level.
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>
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
I have a ListBox:
<ListBox Name="lbsfHolder"
ItemsSource="{Binding UISupportingFunctions}"
SelectedItem="{Binding Path=SelectedSupportedFunction, Mode=TwoWay}"
SelectionMode="Multiple"
IsSynchronizedWithCurrentItem="True"
HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<controls:SupportingFunction GotFocus="SupportingFunction_GotFocus"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In the ViewModel I have:
private SupportingFunction _selectedSupportedFunction;
public SupportingFunction SelectedSupportedFunction
{
get { return _selectedSupportedFunction; }
set
{
_selectedSupportedFunction = value;
NotifyPropertyChanged("SelectedSupportedFunction");
}
}
But when I'm trying to select any item in list box nothing happens. The SelectedItem is null for the ListBox and for SelectedValue, too. Do I need to add some special code to make this work?
UPD:
I've changed views a bit, now I have:
<UserControl x:Class="RFM.UI.WPF.Controls.SupportingFunction">
<Grid>
<ListBox Name="supportingFunctions"
ItemsSource="{Binding UISupportingFunctions}"
SelectedItem="{Binding Path=SelectedSupportedFunction, Mode=TwoWay}"
SelectionMode="Multiple"
IsSynchronizedWithCurrentItem="True"
HorizontalContentAlignment="Stretch">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" />
<ColumnDefinition />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions>
<TextBox Name="tbsfName" Grid.Column="0" Text="{Binding Path=Title, Mode=TwoWay}"></TextBox>
<TextBox Name="tbsfExperssion" Grid.Column="1" Text="{Binding Path=Expression}" HorizontalAlignment="Stretch"></TextBox>
<Button Name="bsfDel" Grid.Column="2">Del</Button>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
</UserControl>
In Page where this control placed:
<StackPanel Name="spSupportingFunctions">
<StackPanel Name="spsfOperations" Orientation="Horizontal">
<Button Name="bsfAdd" Width="30" Command="commands:CustomCommands.AddSupportingFunction">Add</Button>
</StackPanel>
<controls:SupportingFunction DataContext="{Binding Self}" />
</StackPanel>
at code behind of this Page
public PlotDataPage()
{
DataContext = new PlotDataViewModel();
InitializeComponent();
}
and this is the full listing of PlotDataViewModel
public class UISupportingFunction : ISupportingFunction
{
public string Title { get; set; }
public string Expression { get; set; }
}
public class PlotDataViewModel : INotifyPropertyChanged
{
public PlotDataViewModel Self
{
get
{
return this;
}
}
private ObservableCollection<UISupportingFunction> _supportingFunctions;
public ObservableCollection<UISupportingFunction> UISupportingFunctions
{
get
{
return _supportingFunctions;
}
set
{
_supportingFunctions = value;
NotifyPropertyChanged("UISupportingFunctions");
}
}
private UISupportingFunction _selectedSupportedFunction;
public UISupportingFunction SelectedSupportedFunction
{
get
{
return _selectedSupportedFunction;
}
set
{
_selectedSupportedFunction = value;
NotifyPropertyChanged("SelectedSupportedFunction");
}
}
public PlotDataViewModel()
{
UISupportingFunctions = new ObservableCollection<UISupportingFunction>();
}
public void CreateNewSupportingFunction()
{
UISupportingFunctions.Add(new UISupportingFunction() { Title = Utils.GetNextFunctionName() });
}
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
public event PropertyChangedEventHandler PropertyChanged;
}
I'm just calling the CreateNewSupportingFunction() method when I click Add button. Everything looks fine - the items is add and I see them. But when I'm clicking on one of the TextBoxes and then to the bsfDel button right to each item I'm getting null in SelectedSupportedFunction.
Maybe it is because of focus event have been handling by TextBox and not by ListBox?
It's either your ItemsSource UISupportingFunctions is not a SupportingFunction object or you did not set the View's Datacontext to your ViewModel.
ViewModel.xaml.cs
this.DataContext = new ViewModelClass();