I want to separate ListBox from MainMenu by creating UserControl. In MainWindow I'm creating, for testing purposes, ObservableCollection (in code-behind) and in MainWindow.xaml I'm trying to pass this Collection through DataContext to UserControl
MainWindow.xaml.cs
ObservableCollection<ListItem> coll = new ObservableCollection<ListItem>()
{
new TextListItem
{
Content = "Some Text", CreationDate = DateTime.Now, VisibleName = "Title"
}
};
MainWindow.xaml
<userControls:ListBoxUserControl DataContext="{Binding Path=coll}"/>
And in ListBoxUserControl.xaml I'm trying to access Collection like that
<ListBox x:Name="listBox" ItemsSource="{Binding}"/>
But it seems not working. How do I pass Collection through DataContext properly?
coll should be a property on the MainWindow class. As I currently read it, it is just a variable which will go out of scope after you left the method (I think the constructor) in which it is defined.
Related
I have ComboBox in a Datagrid that I bind in a .NET WPF application as follows:
<ComboBox x:Name="categoryValues" MinWidth="70"
IsSynchronizedWithCurrentItem="False"
SelectedValuePath="Id"
ItemsSource="{Binding Source={StaticResource categoryViewSource},
Converter={StaticResource CategoriesToCategoriesConverter}}"
SelectedItem="{Binding Category,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged,
Converter={StaticResource CategoryToCategoryConverter}}"
DisplayMemberPath="Name" SelectionChanged="categoryValues_SelectionChanged">
</ComboBox>
So each row is bound to the field Category in a different Model. The categoryViewSource is defined as follows
<CollectionViewSource x:Key="categoryViewSource" d:DesignSource="{d:DesignInstance {x:Type Models:Category}, CreateList=True}"/>
My Category Model implements INotifyPropertyChanged using PropertyChanged.Fody NuGet package. After I load the data from the database using Entity Framework, I assign the data to the CollectionViewSource as follow:
var db = new MyDbContext();
System.Windows.Data.CollectionViewSource categoryViewSource = (System.Windows.Data.CollectionViewSource)this.Resources["categoryViewSource"];
await db.Categories.LoadAsync();
categoryViewSource.Source = db.Categories.Local;
So it's bound to the Observable collection. However, once I do
var c = new Category { Name="New Category" };
db.Categories.Add(c);
my CollectionViewSource is updated (at least in the debugger), which seems logical, it's source is an ObservableCollection. But even if I do
categoryViewSource.View.Refresh();
my ComboBox ItemsSource isn't updated. I tried with IsSynchronizedWithCurrentItem="True" as seen somewhere on StackOverflow. So far it only works, if I do something like this:
categoryViewSource.Source = null;
categoryViewSource.Source = db.Categories.Local;
But then my SelectedItem property is null in all ComboBoxes and they appear empty in the Datagrid because apparently the instances of SelectedItem are different once you reassign the collection.
Does anyone have a solution. Unfortunately, I'm still really new to WPF and have no idea.
Adding a new Category to the DbSet<Category> doesn't affect the ObservableCollection<Category>. These are two different and independent in-memory collections. You will have to add the same object to both Collections.
You could create an ObservableCollection and populate this one with your entity objects when you initialize your ComboBox. And then you add any new objects to both your context and to your data-bound collection, i.e.:
public partial class MainWindow : Window
{
ObservableCollection<Category> _cats;
public MainWindow()
{
InitializeComponent();
var db = new MyDbContext();
System.Windows.Data.CollectionViewSource categoryViewSource = (System.Windows.Data.CollectionViewSource)this.Resources["categoryViewSource"];
await db.Categories.LoadAsync();
_cats = new ObservableCollection<Category>(db.Categories.Local);
categoryViewSource.Source = _cats;
//...
var c = new Category { Name = "New Category" };
db.Categories.Add(c); //add to DbSet
_cats.Add(c); //add to ObservableCollection
}
}
Ok, if I add it directly as ItemsSource, that does work. However if I add the collection through a separate Binding, it doesn't work. I need to pass the collection through the converter though. Is there a way to do it automatically?
You could set up the binding programmatically:
categoryValues.SetBinding(ComboBox.ItemsSourceProperty, new Binding(".") { Source = _cats, Converter = new CategoriesToCategoriesConverter() });
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.
I'm looking for the best way to populate a check boxes from the following code. I have looked into Binding but not really sure where to go.
Here is the edited code that is working
private void dpDateSelection_SelectedDateChanged(object sender, SelectionChangedEventArgs e)
{
DateTime? date = dpDateSelection.SelectedDate;
logDate = date != null ? date.Value.ToString("yyyy-MM-dd") : null;
dpDateSelection.ToolTip = logDate;
LoadLogs(logDate);
}
private void LoadLogs(string ldate)
{
string[] logs = Directory.GetFiles(logPath + ldate, "*.ininlog");
InitializeComponent();
logList = new ObservableCollection<String>();
logList.Clear();
foreach (string logName in logs)
{
string s = logName.Substring(logName.IndexOf(ldate) + ldate.Length + 1);
int extPos = s.LastIndexOf(".");
s = s.Substring(0, extPos);
logList.Add(s);
}
this.DataContext = this;
}
<ListBox x:Name="Logs" ItemsSource="{Binding logList}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}" ToolTip="{Binding}" Tag="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You will want to start by using an ItemsControl instead of a StackPanel, since ItemsControls are automatically set up to display collections of things:
<ItemsControl ItemsSource="{Binding Logs}"/>
Note the use of ItemsSource. With the accompanying binding string, it basically says "Look for a property on the DataContext called "Logs" and put everything in it into this control".
Next you said you wanted this displayed as checkboxes, so we use an item template:
<ItemsControl ItemsSource="{Binding Logs}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content={Binding .}/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This says "Use a checkbox for each Item in the ItemsSource". The DataTemplate can be a Grid or other collection control as well, so this is a really powerful feature in WPF. The "Binding ." just binds to the object itself (a string in this case, so we don't need a special path).
Finally, you need to set up a property to bind to in your view model:
ObservableCollection<String> Logs {get; set;}
You want an ObservableCollection so that when anything is added to or removed from the list, it automatically updates the UI. If you are going to be completely replacing the list (assignment), then you need to implement INotifyPropertyChanged and invoke the PropertyChanged event in that properties setter.
In your posted loop, you would add each log file to this property.
Also, make sure that somewhere you set the DataContext property of the XAML file (View) to your view model object. If everything is in code behind, use DataContext = this. Note that doing this is considered bad practice, and you should use a separate class (ViewModel).
You didn't mention what you wanted the CheckBoxes to do, so I haven't included anything related to that in my answer. You will likely want to abstract your logs into an object with a "Selected" property you can then bind the IsChecked property of the CheckBoxes to.
Obviously this is a lot to take in, please let me know if I can clarify anything or help further!
Update
You put the property in your ViewModel (DataContext). Whatever class that is, you write:
ObservableCollection<String> Logs {get; set;}
private void LoadLogs()
{
string[] logs = Directory.GetFiles(logPath + logDate, "*.ininlog");
foreach(string logName in logs)
{
string s = logName.Substring(logName.IndexOf(logDate) + logDate.Length + 1);
int extPos = s.LastIndexOf(".");
s = s.Substring(0, extPos);
//MessageBox.Show(s);
Logs.Add(s); //Add the parsed file name to the list
}
}
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
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.