Control different DataContext - c#

Control different DataContext in WPF
As I can deploy multiple DataContext in different tabs and control which is the current DataContext
I'm using Mvvm Light WPF4 i have the different ViewModels, View but i dont know how to handle multiples DataContext and control the current DataContext for change on tab switch
Edit:
I've got an approach to the solution as follows:
Create a ViewModel for the MainView
The tabcontrol source is a ObservableCollection
Each TabItem has its own DataContext
The menu has the DataContext like this: DataContext="{Binding Path=CurrentTab.DataContext}" where CurrentTab change when add new TabItem in the ViewModel
i have the following problems:
how do I connect the ViewModel from the TabControl when you change the tab?
Solution: the problem is that Mvvm Light uses a ViewModelLocator for Binding ViewModel in static way, this is the problem when i add tab in C# the ViewModelLocator dont works, in other way i need load manually the ViewModel for each tab like this:
// in MainModelView.cs
public RelayCommand MyCommand { get; set; }
private void RegisterCommand()
{
MyCommand = new RelayCommand(() =>
{
AddTab("Tab Header", new TabViewModel(), new TabContentControl());
});
}
private void AddTab(string header, object context, ContentControl content)
{
TabItem = null;
foreach(TabItem tab in TabItemList)
{
if(tab.Header.Equals(header);
{
tabItem = tab;
}
}
if(null == tabItem)
{
tabItem = new TabItem();
tabItem.Header = header;
tabItem.Content = content;
tabItem.DataContext = context;
TabItemList.Add(tabItem);
}
CurrentTabIndex = TabItemList.IndexOf(tabItem);
}
2.the DataContext dont update in the menu, my code is wrong?
Solution: the previous point solve this too and only with the follow code solved:
// in RegisterCommands()
ChangeTabCommand = new RelayCommand<TabItem>(tab =>
{
if (null == tab) return;
CurrentTabContext = tab.DataContext;
}
in MainWindow.xml:
<!-- MainWindow.xaml -->
<Button Content="NewTab" Command="{Binding Path=MyCommand }" />
<TabControl
Margin="5 5 5 0"
Grid.Row="1"
ItemsSource="{Binding Path=TabItemList}"
SelectedIndex="{Binding Path=CurrentTabItemIndex}"
x:Name="Workspace">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand
Command="{Binding ChangeTabCommand }"
CommandParameter="{Binding SelectedItem, ElementName=Workspace}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TabControl>
Edit 2:
How to avoid modifying the view in the ViewModel and send the necessary parameters from the same view (ContenControl, Header, Context)

I would create a ViewModelContainer that has a property for each of your view models (e.g. MainViewModel, Tab1ViewModel, Tab2ViewModel).
The you can bind ViewModelContainer as DataContext of the Window and bind each TabItem DataContext to the right VM object in this way DataContext="{Binding Tab1ViewModel}"
No suggestion for problem 2.
Update
Your code does not follow MVVM at 100%. Your command edit the view and viewmodel. If you want to be painstaking the command must interact only with viewmodel. The viewmodel then will signal (through an ObservableCollection or an INotifyPropertyChanged interface) the view that will reply by adding a new tabItem.
I suppose that the View part can be managed 100% with XAML using ItemTemplate to define how the panels should appear.

Related

How to get WPF ListBox to update at start using ItemsSource?

I am very new to WPF and especially to data-binding but I'm trying to populate a ListBox with elements from an external resource, and trying to also follow the MVVM pattern. As such I am trying to avoid any code in my code-behind. I've looked over dozens of other questions similar to this but I feel I am missing something stupid as I cannot get my ListBox to generate with values. I have set the DataContext and then set the Binding for the ItemsSource to the correct property.
Question
How do I simply get this code to populate my empty ListBox when the application starts up?
XAML
<TabItem Name="ServerListTab" Header="Server List">
<TabItem.DataContext>
<viewModel:ServerListViewModel />
</TabItem.DataContext>
<ListBox
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ItemsSource="{Binding ServerList, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding SelectedServer}">
</ListBox>
</TabItem>
ServerList property in view model
public BindingList<string> ServerList
{
get { return _serverListModel.ServerList; }
set
{
if (ReferenceEquals(_serverListModel.ServerList, value)) return;
var aTestServers = //code hidden : gets array correctly from resource
for (var i = 0; i < aTestServers.Count; i++)
{
_serverListModel.ServerList.Add(aTestServers[i]);
}
InvokePropertyChanged("ServerList");
}
}

How to bind a class to a TabItem DataContext in code-behind

I have a class called TabViewModel, and it has properties like Name, etc..
I need to be able to add tabs dynamically, and whenever a new tab is added, I need create a new instance of the TabViewModel and bind it to the new tab.
Here's my code:
XAML:
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
Code behind:
When adding a new tab..
_tabItems = new List<TabItem>();
tabItem.DataContext = ViewModel.CreateNewTabViewModel();
_tabItems.Add(tabItem);
TabControl1.ItemsSource = _tabItems;
TabControl1.SelectedIndex = 0;
So, CreateNewTabViewModel is suppose to create a new TabViewModel and set the Name property to be displayed on the tab header, which is why the TextBlock is bounded to Name.
I also tried tabItem.SetBinding but it didn't work.
Please advice.
Thanks!
_tabItems = new List<TabItem>();
//...
_tabItems.Add(tabItem);
TabControl1.ItemsSource = _tabItems;
Replaces the entire list of tab items with a new list that contains just a single tab item.
That said, the code is not quite clear on what it is doing, a lot seem unneeded. This works:
var tabItems = new List<TabViewModel>();
tabItems.Add(new TabViewModel { Name = "MyFirstTab" });
myTabControl.ItemsSource = tabItems;
myTabControl.SelectedIndex = 0;
All you need to do is add an instance of a view model to a list of view models and point the tab control to use it. There is no need to set the data context; by setting the items source you are implicitly setting the datacontext of each tab to an item in the collection.

WPF - switching between two way and one way bindings?

In my 'View' I have a TextBox bound to a ViewModel's string property.
I want to add a submit button to the View, so the underlying ViewModels string property is only updated when this is pressed.
To further complicate things, this TextBox is inside a DataGrid. I think setting the bindings UpdateSourceTrigger to Explicit may be the answer but I can't see how this would work.
Any alternative solution would be to switch the ViewModels String with a TextBox - meaning I would manually populate data.
You can bind button to command and pass text of textbox as parameter.
<TextBox x:Name="textBox"></TextBox>
<Button Content="Button" Command="{Binding MyCommand}" CommandParameter="{Binding ElementName=textBox, Path=Text}"/>
In your ViewModel:
public ICommand MyCommand
{
get
{
return new RelayCommand((textBoxText) =>
{
if (...)
{
//somelogic;
}
});
}
}
In the Button you access the row via the DataContext
private void ButtonRevise_Click(object sender, RoutedEventArgs e)
{
Button btn = (Button)sender;
GabeLib.SearchItem srchItem = (GabeLib.SearchItem)btn.DataContext;

ICollectionView's SourceCollection is null

I have a ViewModel with two ICollectionViews which are bound as ItemsSources to two different ListBoxes. Both wrap the same ObservableCollection, but with different filters. Everything works fine initially and both ListBoxes appear properly filled.
However when I change an item in the ObservableCollection and modify a property which is relevant for filtering, the ListBoxes don't get updated. In the debugger I found that SourceCollection for both ICollectionVIews is null although my ObservableCollection is still there.
This is how I modify an item making sure that the ICollectionViews are updated by removing and adding the same item:
private void UnassignTag(TagViewModel tag)
{
TrackChangedTagOnCollectionViews(tag, t => t.IsAssigned = false);
}
private void TrackChangedTagOnCollectionViews(TagViewModel tag, Action<TagViewModel> changeTagAction)
{
_tags.Remove(tag);
changeTagAction.Invoke(tag);
_tags.Add(tag);
}
The mechanism works in another context where I use the same class.
Also I realized that the problem disappears if I register listeners on the ICollectionViews' CollectionChanged events. I made sure that I create and modify them from the GUI thread and suspect that garbage collection is the problem, but currently I'm stuck... Ideas?
Update:
While debugging I realized that the SourceCollections are still there right before I call ShowDialog() on the WinForms Form in which my UserControl is hosted. When the dialog is shown they're gone.
I create the ICollectionViews like this:
AvailableTags = new CollectionViewSource { Source = _tags }.View;
AssignedTags = new CollectionViewSource { Source = _tags }.View;
Here's how I bind one of the two (the other one is pretty similar):
<ListBox Grid.Column="0" ItemsSource="{Binding AvailableTags}" Style="{StaticResource ListBoxStyle}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Style="{StaticResource ListBoxItemBorderStyle}">
<DockPanel>
<Button DockPanel.Dock="Right" ToolTip="Assign" Style="{StaticResource IconButtonStyle}"
Command="{Binding Path=DataContext.AssignSelectedTagCommand, RelativeSource={RelativeSource AncestorType={x:Type tags:TagsListView}}}"
CommandParameter="{Binding}">
<Image Source="..."/>
</Button>
<TextBlock Text="{Binding Name}" Style="{StaticResource TagNameTextBlockStyle}"/>
</DockPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I use MvvmLight's RelayCommand<T> as ICommand implementation in my ViewModel:
AssignSelectedTagCommand = new RelayCommand<TagViewModel>(AssignTag);
I had this issue too, with a similar use-case. When I updated the underlying collection, I would call Refresh() on all the filtered views. Sometimes, this would result in a NullReferenceException thrown from within ListCollectionView.PrepareLocalArray() because SourceCollection is null.
The problem is that you shouldn't be binding to the CollectionView, but to the CollectionViewSource.View property.
Here's how I do it:
public class ViewModel {
// ...
public ViewModel(ObservableCollection<ItemViewModel> items)
{
_source = new CollectionViewSource()
{
Source = items,
IsLiveFilteringRequested = true,
LiveFilteringProperties = { "FilterProperty" }
};
_source.Filter += (src, args) =>
{
args.Accepted = ((ItemViewModel) args.Item).FilterProperty == FilterField;
};
}
// ...
public ICollectionView View
{
get { return _source.View; }
}
// ...
}
The reason for your issue is that the CollectionViewSource is getting garbage collected.

User Control in Pivot, binding not work

I'm developing windows phone 8 app. I have a customer UserControl called SelectableButton. The constructor of it is as below:
public SelectableButton()
{
InitializeComponent();
DataContext = this;
}
The xaml of it is like this:
<Grid>
<TextBlock x:Name="ButtonTextBlock"
Text="{Binding SelectableButtonText, Mode=TwoWay}"
SomeOtherCode
/>
...
</Grid>
The SelectableButtonText is a property of this UserControl:
public static readonly DependencyProperty SelectableButtonTextProperty =
DependencyProperty.Register(
"SelectableButtonText", typeof(string),
typeof(SelectableButton),
null
);
Now I use this SelectableButton in a Pivot. I want to bind the SelectableButtonText property to some data. This is the DataTemplate used in a Pivot called PivotTestContent:
<ShareControl:SelectableButton
SelectableButtonText="{Binding question}"
...
>
</ShareControl:SelectableButton>
The question is from the ItemsSource of this Pivot:
PivotTestContent.ItemsSource = quizs;
The quizs is a List<> of WCCQuizText
quizs = new List<WCCQuizText>();
And the question is a property member of WCCQuizText:
public String question
{
get;
set;
}
After all these work, I find that the Binding cant find the property question. It seems that because of this line in the constructor of SelectableButton:
DataContext = this;
The Binding will look for the property question in Class SelectableButton, not from the ItemsSouce. Because if I bind question directly to some TextBlock.Text, it will work. But when I bind it to my UserControl, it can't be found.
So anybody know how to deal with this?
If I do like this, I can show the binding text correctly, the TextBlock is in the Pivot, too.
<TextBlock
Name="TextBlockQuestion"
Text="{Binding question}"
....
>
</TextBlock>
And my Binding:
<ShareControl:SelectableButton
SelectableButtonText="{Binding Text, ElementName=TextBlockQuestion}"
....
>
</ShareControl:SelectableButton>
You are correct. It is caused by DataContext = this. Normally your UserControl would have context set to an instance of WCCQuizText but you are overwriting it with an instance of your UserControl. Try removing that line, give UserControl some name and and change your binding, within UserControl, to something like:
<UserControl x:Name="SomeName" ... >
....
<TextBlock ... Text="{Binding ElementName=SomeName, Path=SelectableButtonText}"
also TextBlock is display control and it will always be one way binding so you can skip Mode=TwoWay

Categories

Resources