I'm using MahApps for my first Wpf project, and more specifically tabControls. I tested it on a tiny test project. It worked well, until I tried to merge my code in my project.
Here's an example of my code behind :
public class Datas
{
public ObservableCollection<string> titles { get; set; }
public Datas()
{
init();
}
public void init()
{
titles = new ObservableCollection<string>()
{
"Title 1",
"Title 2",
"Title 3",
"Title 4"
};
}
}
public partial class Window1 : MetroWindow
{
private Datas datas;
public Window1()
{
init();
}
private void init()
{
datas = new Datas();
this.DataContext = this;
}
}
And my Xaml code :
<TabControl DataContext="{Binding Datas}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding titles}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBlock Text="Content" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
I've been searching for few days so far. I've found a topic about dataBinding to another class but it doesn't seem to work for me. Not sure if I can use both DataContexts in my Window1 class, even if I tried something like binding multiple controls to different dataContexts.
Do I really need something like that ? It seems to be bigger than what I need but I may be wrong. So, my problem is that I would like to have my tabs whose titles are those in my list, and it doesn't display anything (no error when running though).
Thanks for your help, and please be slow in your answers, I'm still new to Wpf :)
The Window:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TabControl ItemsSource="{Binding Titles}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBlock Text="Content" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
The Window's code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
this.DataContext = new Datas();
this.InitializeComponent();
}
}
public class Datas
{
private ObservableCollection<String> titles;
public ObservableCollection<String> Titles
{
get
{
if (this.titles == null)
{
this.titles = new ObservableCollection<String>()
{
"Title 1",
"Title 2",
"Title 3",
"Title 4"
};
}
return this.titles;
}
}
}
Some advices:
Binding only works with public properties.
Do not expose an ObservableCollection if you are not going to modify it, use IList instead (it is lighter).
In WPF, at least for properties, lazy initialization is more natural than constructor initialization (see the Titles property).
Take a look at the .NET capitalization conventions, prefer using Pascal Casing for properties.
Set ItemsSource of TabControl and defind public property Datas in your Window's DataContext
<TabControl ItemsSource="{Binding Datas.titles}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</TabControl.ItemTemplate>
public partial class Window1 : MetroWindow
{
public Window1()
{
init();
}
private void init()
{
Datas = new Datas();
this.DataContext = this;
}
public Datas Datas{get;set;}
}
Related
I am trying to create a TabControl that can have multiple pages with the same input fields.
I have combined the input fields in a UserControl called TabContent. I have created a TabControl in the MainWindow, and I want to integrate TabItem headers from the MainWindow via the ItemTemplate resp. DataTemplate. In the MainWindow, I create a list of TabContents in which text is assigned to the headers. This text is not displayed by the running programme as shown in the screenshot. Any idea why and how to fix this?
Screenshot with empty TabItem Headers
Below is a minimal code example that reproduces the error.
// UserControl1.xaml.cs
namespace Nur_Test
{
public partial class TabContent : UserControl
{
public TabContent()
{
InitializeComponent();
}
public string Header { get; set; }
}
}
// UserControl1.xaml
<UserControl x:Class="Nur_Test.TabContent"
// xmlns= [omitted...]
xmlns:local="clr-namespace:Nur_Test"
mc:Ignorable="d"
d:DesignHeight="200" d:DesignWidth="400">
<Grid>
<TextBox>Test</TextBox>
</Grid>
</UserControl>
// MainWindow.xaml
<Window x:Class="Nur_Test.MainWindow"
// xmlns= [omitted...]
xmlns:local="clr-namespace:Nur_Test"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TabControl x:Name="tabControl1">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Grid>
</Window>
// MainWindow.xaml.cs
namespace Nur_Test
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<TabContent> items = new List<TabContent>()
{
new TabContent() { Header = "Item 1" },
new TabContent() { Header = "Item 2" },
new TabContent() { Header = "Item 3" },
};
tabControl1.ItemsSource = items;
}
public class TabItemData
{
public string Header { get; set; }
}
}
}
It seems the binding that does not work. Putting a fixed string into the TextBlock instead of {Binding Header} in MainWindow.xaml works fine (but does not solve my problem):
<TextBlock Text="12345" />
Sorry if this is a stupid question, I'm still a beginner when it comes to C# :-)
Set the ItemsSource to a List<TabItemData>:
List<TabItemData> items = new List<TabItemData>()
{
new TabItemData() { Header = "Item 1" },
new TabItemData() { Header = "Item 2" },
new TabItemData() { Header = "Item 3" },
};
tabControl1.ItemsSource = items;
...and then use the UserControl in the ContentTemplate:
<TabControl x:Name="tabControl1">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<local:TabContent />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
You are not supposed to set or bind the ItemsSource to a collection of UI elements such as UserControls.
Your advice helped me a lot, mm8. In order for the code to finally work the way I wanted, I also had to create the class TabItemData. And I also needed to bind the content so that it would change when the tabs were clicked. Now it seems to work. Thank you very much!
Here are the final code modifications that worked for me:
// MainWindow.xaml.cs
public class TabItemData
{
public string Header { get; set; }
public TabContent Content { get; set; }
}
and
// MainWindow.xaml.cs
List<TabItemData> items = new List<TabItemData>()
{
new TabItemData() { Header = "Item 1", Content = new TabContent() },
new TabItemData() { Header = "Item 2", Content = new TabContent() },
new TabItemData() { Header = "Item 3", Content = new TabContent() }
};
tabControl1.ItemsSource = items;
plus
// MainWindow.xaml
<TabControl x:Name="tabControl1" Grid.Row="2" Grid.Column="0" Margin="10,0">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
i have MyPage.xaml and MyPage.xaml.cs files.
Into .xaml file i written a data template like this:
<DataTemplate x:Key="MyDataTemplate" x:DataType="local:MyClass">
...
<TextBlock Text="{x:Bind name}" HorizontalAlignment="Left" TextWrapping="Wrap"/>
...
</DataTemplate>
I can bind name attribute of MyClass correctly.
Now i need to bind an attribute of .xaml.cs file but DataTemplate show me only MyClass data. How can i bind data from .xaml.cs page? Outside DataTemplate (but in the same xaml file) i can see any attribute of .xaml.cs file.
I need to bind a List of objects to a combobox like this:
<ComboBox ItemsSource="{x:Bind myList}" HorizontalAlignment="Left"></ComboBox>
but myList is an .xaml.cs attribute.
I want to view a string name attribute of objects of the list.
Thank you for your help
uwp: how to bind data inside DataTemplate outside of x:DataType?
For your recrement, we suggest you use Binding to replace x:Bind, You could use Binding to access current root DataContext with ElementName.
For example
<Grid x:Name="GridRoot">
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<ComboBox ItemsSource="{Binding ElementName=GridRoot, Path=DataContext.Options}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Code Behind
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.DataContext = this;
}
public List<string> Options { get; set; } = new List<string>() {"One","Two","Three" };
public List<Item> Items { get; set; } = new List<Item>()
{
new Item { Name = "HH" },
new Item { Name = "ZZ" },
new Item { Name = "MM" }
};
}
public class Item
{
public string Name { get; set; }
}
So I just setup a project and added a custom UserControl that looks like this.
<Grid>
<ItemsControl ItemsSource="{Binding UserViewModel.Users}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<controls:UserCard/>
<TextBlock Text="{Binding Name}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
As you can see I tried binding the Text property buti it doesn't bind.
Now there could be a lot of reasons to why it's behaving like this so I will try to narrow it down.
I've created a BaseViewModel that will hold my ViewModels and it looks like this.
public class BaseViewModel : ObservableObject
{
public UserViewModel UserViewModel { get; set; } = new UserViewModel();
}
And then I've setup my ViewModel like this
public class UserViewModel : ObservableObject
{
public ObservableCollection<User> Users { get; set; } = new ObservableCollection<User>();
public UserViewModel()
{
Users.Add(new User{Name = "Riley"});
Users.Add(new User{Name = "Riley1"});
}
}
Simple, now I do have a ObservableObject that looks like this and deals with the INPC
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And in my MainView.xaml
I've set the DataContext like so
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new BaseViewModel();
}
}
It's the exact same for the UserControl
And this is where I actually add the UserControl so it displays in the MainWindow
<ItemsControl ItemsSource="{Binding UserViewModel.Users}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<controls:UserCard/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Now the issue is that it doesn't bind the Data, I want to display the Name property from the Model but it's not displaying it and I am not sure why, if I try to bind it to a TextBlock property in the MainView directly it works fine.
I am unsure to why it's behaving like this and I would like to understand why.
Do I need to make use of DependencyProperties? Or is it just a case of me creating a new instance of the BaseViewModel? Where did I go wrong?
Your MainViewWindow contains an ItemsControl with the binding ItemsSource="{Binding UserViewModel.Users}", with each item being displayed with a <controls:UserCard/>. But your user control is then trying to bind to the list again with "{Binding UserViewModel.Users}". Why are you trying to display a list inside another list?
I suspect the problem here is that you think your custom UserControl's DataContext is still pointing to the BaseViewModel, like its parent. It isn't. The DataContext of each item in an ItemsControl points to it's own associated element in the list, i.e. an instance of type User.
UPDATED: Let's say you have a main view model with a list of child view models, like this:
public class MainViewModel : ViewModelBase
{
public MyChildViewModel[] MyItems { get; } =
{
new MyChildViewModel{MyCustomText = "Tom" },
new MyChildViewModel{MyCustomText = "Dick" },
new MyChildViewModel{MyCustomText = "Harry" }
};
}
public class MyChildViewModel
{
public string MyCustomText { get; set; }
}
And let's say you set your MainWindow's DataContext to an instance of MainViewModel and add a ListView:
<ListView ItemsSource="{Binding MyItems}" />
If you do this you'll see the following:
What's happening here is that the ListView is creating a container (of type ContentPresenter) for each of the three elements in the list, and setting each one's DataContext to point to its own instance of MyChildViewModel. By default ContentPresenter just calls 'ToString()' on its DataContext, so you're just seeing the name of the class it's pointing to. If you add a ToString() operator to your MyChildViewModel like this:
public override string ToString()
{
return $"MyChildViewModel: {this.MyCustomText}";
}
... then you'll see that displayed instead:
You can also override the ListViewItem's template entirely, and since it already points to its associated instance of MyChildViewModel you can just bind directly to its properties:
<ListView ItemsSource="{Binding MyItems}">
<ListView.ItemTemplate>
<DataTemplate>
<!-- One of these gets created for each element in the list -->
<Border BorderBrush="Black" BorderThickness="1" Background="CornflowerBlue" CornerRadius="5" Padding="5">
<TextBlock Text="{Binding MyCustomText}" Foreground="Yellow" />
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Which will change the display to this:
Make sense?
I am building a WPF application with mahapps, prism[modularity]. I have below HomeWindow.xaml code.
<Controls:MetroWindow x:Class="Project.Views.HomeWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:local="clr-namespace:Project.Views"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
<!--The above code is for automatically binding of viewmodel into view-->
Height="700" Width="1200" Background="White">
<Grid>
<TabControl ItemsSource="{Binding TabCollection}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock Text="{Binding Name}"/>
</TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<Label Content="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Controls:MetroWindow>
I have below structure in my HomeViewModel.cs under ViewModels directory.
public class HomeViewModel : BindableBase
{
private ObservableCollection<Item> _tabCollection;
public ObservableCollection<Item> TabCollection { get { return _tabCollection; } set { SetProperty(ref _tabCollection, value); } }
//Prism way of getting and setting data
}
public class Item
{
private string Name;
private string Content;
public Item(string name, string content)
{
Name = name;
Content = content;
}
}
below is how I add data into TabCollection property through HomeWindow.xaml.cs.
private HomeViewModel _model=new HomeViewModel();
public HomeWindow(EmployeeViewModel model)
{
InitializeComponent();
_model.UserViewModel = model;
LoadHomeData(_model.UserViewModel.EmpRole);
DataContext = this;
}
private void LoadHomeData(string Role)
{
if (string.Equals(Role, "Admin"))
{
_model.TabCollection= new ObservableCollection<Item>()
{
new Item("Test1", "1"),
new Item("Test2", "2"),
new Item("Test3", "3")
};
}
}
Now matter what, the tabs will not get displayed. Its a blank empty window. I have followed the example in the issue here and have went through few similar posts having same kind of approach. But none of them helped. Is this because of prism way of databinding or is there anything else am missing here? Hope to find some help on this..
Your problem is not connected to MahApps or Prism but to how WPF works in general. In your case Name and Content are private fields and should be public properties
public string Name { get; set; }
public string Content { get; set; }
private or field is not a valid binding source. You can find more as to what is a valid binding source under Binding Sources Overview but in your case, as far as CLR object goes:
You can bind to public properties, sub-properties, as well as indexers, of any common language runtime (CLR) object. The binding engine uses CLR reflection to get the values of the properties. Alternatively, objects that implement ICustomTypeDescriptor or have a registered TypeDescriptionProvider also work with the binding engine.
Another problem is that DataContext is set wrong. At the moment is set to HomeWindow and I think it should be set to instance of HomeViewModel which holds TabCollection property
DataContext = _model;
I try to understand (without success) why binding behaves differentially when source object is string[] and List<string>. I have two lists, their only difference is ItemsSource - in one case array in second List:
XAML code:
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Button Content="Modify items" Click="Button_Click"/>
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<DataTemplate x:Key="ItemTemplate">
<TextBlock Text="{Binding}" FontSize="16"/>
</DataTemplate>
</StackPanel.Resources>
<ListView ItemsSource="{Binding ArrayElements}" ItemTemplate="{StaticResource ItemTemplate}" Width="100" Margin="20"/>
<ListView ItemsSource="{Binding ListElements}" ItemTemplate="{StaticResource ItemTemplate}" Width="100" Margin="20"/>
</StackPanel>
</StackPanel>
Code behind:
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaiseProperty(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
public string[] ArrayElements { get; } = new string[] { "Standard", "Standard", "Standard" };
public List<string> ListElements { get; } = new List<string> { "Standard", "Standard", "Standard" };
public MainPage() { this.InitializeComponent(); this.DataContext = this; }
private void Button_Click(object sender, RoutedEventArgs e)
{
ArrayElements[1] = "Modified one";
ListElements[1] = "Modified one";
RaiseProperty(nameof(ArrayElements));
RaiseProperty(nameof(ListElements));
}
}
Once I click button, the first list (build upon array) gets refilled with new elements (seems like new ItemsSource), so I can see a little flicker and the change in second element.
The same time in the second list nothing changes - the reference to binding source doesn't change so we don't see the difference on screen (PropertyChange has no effect as no property has been changed) - that's clear.
So why the list where the ItemsSource is set to array behaves different?
Sample to reproduce the issue (though almost whole code is above)
It sounds weird to me but i never faced this Problem because i use ElementViewModel objects in an observable collection. Instead of changing the list-item / Array element i Change a property of the ElementViewModel and use the INotifyProperty Change there. It's not the actuall answer to you question but you can work around with this.