Dynamically load TabItem - wpf

I have 2 xaml files: MainWindow.xaml and Subscreen.xaml.
I want to dynamically load Subscreen.xaml as a TabItem under TabControl of MainWindow.xaml.
There are a ViewModel for each View.
MainWindow.xaml: MainViewModel
SubScreen.xaml: SubViewModel
The following is the test code:
[TestMethod]
public void MyTest2()
{
// Arrange
// Initilize a subVM of CMSEditorViewModel type
var subVM = new SubViewModel();
// Initialize a mainVM of CMSEditorMainViewModel type
var mainVM = new MainViewModel();
// Initialize a MainWindow of DynamicCMS.Exe.CMSEditor
var mainWindow = new MyEditor.MainWindow();
mainWindow.DataContext = mainVM;
ContentPresenter presenter = new ContentPresenter();
using (var stream = System.IO.File.OpenRead(CmsPath.DirViewWithBS + "Subscreen.xaml"))
{
DataTemplate template = XamlReader.Load(stream) as DataTemplate;
presenter.ContentTemplate = template;
presenter.Content = subVM;
}
// Create a TabItem of TabControl
TabItem item = new TabItem();
item.Header = "Tab1";
item.Content = presenter;
item.Name = "tab1";
// Get "mainTabControl" TabControl from MainWindow
CustomTabControl tab = CmsUtil.GetControl((Visual)mainWindow.Content, "mainTabControl") as CustomTabControl;
// Add TabItem to TabControl
tab.Items.Add(item);
// Act
CustomTabControl customTabControlEditor = (CustomTabControl)CmsUtil.GetControl((Visual)mainWindow.Content, "EditTabControl");
// Assert
Assert.IsNotNull(customTabControlEditor);
}
The "mainTabControl" is defined in MainWindow.xaml -> I can get this "mainTabControl" TabControl.
The "EditTabControl" is defined in Subscreen.xaml -> I can't get this "EditTabControl" TabControl -> customTabControlEditor is null.
Is anything wrong in the code that loading dynamically?
Thanks in advance.

Related

How to rebuild logical tree in WPF without showing window

I have a test code that add a TabItem to TabControl.
But when I try to find the TabItem by name, null is return.
I found a solution is show the window, then I can find the TabItem by name.
But when many tests are running, OutOfMemory exception is occurred because many windows are opened.
Is there another solution to rebuild logical tree without showing window?
The following is my test code
[TestMethod]
public void MyTest2()
{
// Arrange
// Initilize a subVM of CMSEditorViewModel type
var subVM = new SubViewModel();
// Initialize a mainVM of CMSEditorMainViewModel type
var mainVM = new MainViewModel();
// Initialize a MainWindow of DynamicCMS.Exe.CMSEditor
var mainWindow = new MyEditor.MainWindow();
mainWindow.DataContext = mainVM;
ContentPresenter presenter = new ContentPresenter();
using (var stream = System.IO.File.OpenRead(CmsPath.DirViewWithBS + "Subscreen.xaml"))
{
DataTemplate template = XamlReader.Load(stream) as DataTemplate;
presenter.ContentTemplate = template;
presenter.Content = subVM;
}
// Create a TabItem of TabControl
TabItem item = new TabItem();
item.Header = "Tab1";
item.Content = presenter;
item.Name = "tab1";
// Get "mainTabControl" TabControl from MainWindow
CustomTabControl tab = CmsUtil.GetControl((Visual)mainWindow.Content, "mainTabControl") as CustomTabControl;
// Add TabItem to TabControl
tab.Items.Add(item);
mainWindow.Show() // After showing window, I can find the TabItemControl
// Act
TabItem tabItem = (TabItem)CmsUtil.GetControl((Visual)mainWindow.Content, "tab1");
// Assert
Assert.IsNotNull(tabItem);
}

Window FontSize is not inherited in child control

In certain cases I have the problem that the FontSize that I set on a WPF window is not inherited to a child control.
It happens, if a custom user control sets its content (e.g. a Label) upon changing the DataContext.
I can reproduce this when putting this UserControl into a new window, then close this window and create a new one having the same UserControl in it (see the following code).
In my complex application it's a custom popup window and a custom UserControl that changes its content if the DataContext is changed. There the font is not inherited upon the first open of the window (so the usercontrol hasn't been in another visual/logical tree until that) but I can't reproduce this in a small test application.
public partial class App : Application
{
// App.xaml: ShutdownMode="OnExplicitShutdown"
private void Application_Startup(object sender, StartupEventArgs e)
{
var testControl = new TestControl();
var w = new Window();
w.FontSize = 40;
w.DataContext = this;
w.Content = testControl; // TestControl.DataContextChanged creates label which has FontSize = 40
w.Show();
w.Close();
w.DataContext = null;
//w.Content = null; // if this is done, the font will be correct (40)
w = null;
w = new Window();
w.FontSize = 40;
w.DataContext = this;
//testControl.DataContext = this; // if this is done, the font will be correct (40)
w.Content = testControl; // TestControl.DataContextChanged creates label with remaining FontSize = 12 (Default)
w.Show();
}
}
public class TestControl : UserControl
{
public TestControl()
{
DataContextChanged += TestControl_DataContextChanged;
}
private void TestControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null) Content = new Label() { Content = "TestControllabel"};
else Content = null;
}
}
I'm not looking for a fix of this example app, but for the reason why the font size is not inherited in this special case, so maybe then I can fix my complex app.
Any thoughts would be useful !
Edit: For now I fixed my application by setting the datacontext of the control before setting it as window content.

WPF user control, access dependency properties of component elements

Problem
A user WPF control is made up of multiple standard controls.
How can multiple dependency properties of the component (base or standard) controls be accessed in XAML, when implementing the parent (user) control, without creating additional properties?
Details
What do I mean by "creating additional dependency properties"? Well, that is the only way I know of accessing properties of the component controls: by implementing attached properties, as described at MSDN here.
However, it presents the following problems:
Existing dependency properties must be copied as new properties, defeating the DRY principle.
If data binding is to occur, more work must be done to bind existing dependency properties to the new exposed dependency properties.
I'm wondering if there is a way to "walk" the base controls within the user control, to access their properties - from within XAML.
Example
For example, I make a user WPF control that inherits from UserControl. It is simple - it consists of a StackPanel containing a Label and a TextBlock:
<UserControl x:Class="MyApp.CustomControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<Label Name="BaseLabel">Label Here</Label>
<TextBlock Name="BaseTextBlock">Some text here.</TextBlock>
</StackPanel>
</UserControl>
Now, when I use my UserControl elsewhere in XAML, I'm wishfully thinking something like this could be done to edit my Label's content... although I don't know of a way:
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyApp">
<StackPanel>
<!-- This won't work, don't try at home kids. -->
<local:CustomControl BaseLabel.Content="I did it!"></local:CustomControl>
</StackPanel>
</Window>
Much thanks.
How about the next solution:
1. Create the AttachedProperty (because you must an entry point) and bind this property to the collection of data.This collection of data will contain changes you want perform on sub-controls of a main user control used inside the window. This collection will be defined inside the main window view model.
2. In attached property changed callback get the binded collection, parse it data into sub-controls properties.
Here is the solution:
3. Xaml code:
<Window.DataContext>
<nirHelpingOvalButton:MainWindowViewModel />
</Window.DataContext>
<Grid>
<nirHelpingOvalButton:InnerControl x:Name="MyInnerControl"
nirHelpingOvalButton:Helper.InnerControlPropertiesAccessor="{Binding InnerData, Mode=Default, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
4. Attached property code (bindig support):
public static readonly DependencyProperty InnerControlPropertiesAccessorProperty = DependencyProperty.RegisterAttached(
"InnerControlPropertiesAccessor", typeof (ObservableCollection<TargetControlData>), typeof (Helper), new PropertyMetadata(default(ObservableCollection<TargetControlData>), InnerValueAccessProviderPropertyChangedCallback));
public static void SetInnerControlPropertiesAccessor(DependencyObject element, ObservableCollection<TargetControlData> value)
{
element.SetValue(InnerControlPropertiesAccessorProperty, value);
}
public static ObservableCollection<TargetControlData> GetInnerControlPropertiesAccessor(DependencyObject element)
{
return (ObservableCollection<TargetControlData>) element.GetValue(InnerControlPropertiesAccessorProperty);
}
private static void InnerValueAccessProviderPropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var control = sender as Control;
if (control == null) return;
var valuesMap = args.NewValue as ObservableCollection<TargetControlData>;
if (valuesMap == null)
return;
valuesMap.ToList().ForEach(data => TryToBind(control, data));
}
private static void TryToBind(Control control, TargetControlData data)
{
var innerControl = control.FindName(data.SubControlName) as DependencyObject;
if (innerControl == null) return;
var myBinding = new Binding
{
Source = data,
Path = new PropertyPath("Data"),
Mode = BindingMode.TwoWay,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
var descriptors = TypeDescriptor.GetProperties(innerControl);
var propertyDescriptor = descriptors.Find(data.SubConrolProperty, true);
var descriptor = DependencyPropertyDescriptor.FromProperty(propertyDescriptor);
if (descriptor == null) return;
var dependencyProperty = descriptor.DependencyProperty;
BindingOperations.SetBinding(innerControl, dependencyProperty, myBinding);
}
5. Inner control xaml:
<UserControl x:Class="NirHelpingOvalButton.InnerControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UniformGrid>
<Button x:Name="InnerControlButton"></Button>
<TextBlock x:Name="InnerContentTextBlock"></TextBlock>
</UniformGrid>
6. ViewModel code:
public class MainWindowViewModel:BaseObservableObject
{
private static int _staticCount = 0;
private List<Brush> _list = new List<Brush> {Brushes.Green, Brushes.Red, Brushes.Blue};
public MainWindowViewModel()
{
InnerData = new ObservableCollection<TargetControlData>
{
new TargetControlData
{
SubControlName = "InnerControlButton",
SubConrolProperty = "Content",
Data = "Click Me",
},
new TargetControlData
{
SubControlName = "InnerControlButton",
SubConrolProperty = "Command",
Data = new RelayCommand(CommandMethod),
},
new TargetControlData
{
SubConrolProperty = "Text",
SubControlName = "InnerContentTextBlock",
Data = "Hello"
},
new TargetControlData
{
SubConrolProperty = "Background",
SubControlName = "InnerContentTextBlock",
Data = Brushes.Green
},
new TargetControlData
{
SubConrolProperty = "Foreground",
SubControlName = "InnerContentTextBlock",
Data = Brushes.White
},
};
}
private void CommandMethod()
{
_staticCount ++;
var backgroundData = InnerData.FirstOrDefault(data => data.SubControlName == "InnerContentTextBlock" && data.SubConrolProperty == "Background");
var textData = InnerData.FirstOrDefault(data => data.SubControlName == "InnerContentTextBlock" && data.SubConrolProperty == "Text");
if (backgroundData == null || textData == null) return;
var index = _staticCount%_list.Count;
backgroundData.Data = _list[index];
textData.Data = string.Format("{0} {1}", "Hello", backgroundData.Data);
}
public ObservableCollection<TargetControlData> InnerData { get; set; }}
7. TargetControlData code:
public class TargetControlData:BaseObservableObject
{
private string _subControlName;
private string _subConrolProperty;
private object _data;
public string SubControlName
{
get { return _subControlName; }
set
{
_subControlName = value;
OnPropertyChanged();
}
}
public string SubConrolProperty
{
get { return _subConrolProperty; }
set
{
_subConrolProperty = value;
OnPropertyChanged();
}
}
public object Data
{
get { return _data; }
set
{
_data = value;
OnPropertyChanged();
}
}
}
Summary - you can pull control properties data from configuration file, or collect them by reflection.
regards,
The way you suggested - I don't think this would be possible.
But it can be done with normal properties, instead of dependency properties, something like:
UserControl xaml:
<StackPanel>
<TextBlock x:Name="tbOne"></TextBlock>
<TextBlock x:Name="tbTwo" Foreground="Red"></TextBlock>
</StackPanel>
UserControl code behind:
public string One
{
get
{
return this.tbOne.Text;
}
set
{
this.tbOne.Text = value;
}
}
public string Two
{
get
{
return this.tbTwo.Text;
}
set
{
this.tbTwo.Text = value;
}
}
and the usage of user control:
<local:UserControl1 One="test1" Two="test2"></local:UserControl1>

How can you manually set the DataContext for the items of an ItemsControl?

I'd like to be able to manually set the DataContext of the container used for the items of an ItemControl?
is this possible?
(I don't want to do this using a converter for the ItemsSource as the collection changes, and ideally I'd like to be able to do it in some kind of custom ItemsControl as it will be used frequently)
<l:CustomItemsControl ItemsSource="{Binding Items}"/>
public partial class MainWindow : Window
{
public ObservableCollection<object> Items { get; private set; }
public MainWindow()
{
Items = new ObservableCollection<object>() { 6, "Dog", DateTime.Now };
DataContext = this;
InitializeComponent();
}
}
I thought I could do something like below, but it doesn't have any affect as it appears the DataContext is set again afterwards
public class CustomItemsControl : ItemsControl
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
var presenter = element as ContentPresenter;
presenter.DataContext = 5;
// below would be some binding that is set up on some dynamic property of item
// presenter.SetBinding(ContentPresenter.DataContextProperty, new Binding());
}
}

MVVM gridview binding to datatable WPF

I am new to MVVM and databinding and I am having some trouble binding a gridview to a datatable dynamically. I am able to get the column headers to bind, but no data is being displayed in the grid itself.
My model simply returns a data table as the result of a SQL string passed to it.
My viewmodel just wraps the datatable and gets bound to the view.
Right now I am just trying to display the data by populating the gridview from the main window, but only the headers are being displayed.
I know there is data in the model.Results datatable though.
My viewmodel:
public class ResultsViewModel
{
private DataTable _dt;
public ResultsViewModel()
{
DataSource _ds = new DataSource();
_dt = _ds.Execute("select * from tbl_users");
}
public DataTable Results
{
get { return _dt; }
set { _dt = value; }
}
}
My code to populate the gridview from the mainwindow:
public MainWindow()
{
InitializeComponent();
ResultsView view = new ResultsView();
ResultsViewModel model = new ResultsViewModel();
GridView Grid = new GridView();
foreach (DataColumn col in model.Results.Columns)
{
Grid.Columns.Add(new GridViewColumn
{
Header = col.ColumnName,
DisplayMemberBinding = new Binding(col.ColumnName)
});
}
view._listView.View = Grid;
view.DataContext = model;
view.SetBinding(ListView.ItemsSourceProperty, new Binding());
_placeholder.Content = view;
}
The ResultsView xaml:
<UserControl x:Class="InDevReporting.Views.ResultsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<ListView x:Name="_listView" />
</Grid>
Try setting your data context to model.Results.
ie change this line:
view.DataContext = model;
to this:
view.DataContext = model.Results;
Generally you would create a dependency property on your view model and specify the binding in the XAML. The grid should be clever enough to figure out what columns to draw:
<ListView ItemsSource="{Binding Results}" />
public MainWindow()
{
InitializeComponent();
// your code to instance and populate model
this.DataContext = model;
}
public class ResultsViewModel : DependencyObject
{
public static readonly DependencyProperty ResultsProperty = DependencyProperty.Register("Results", typeof(DataTable) , typeof(ResultsViewModel));
public DataTable Results
{
get { (DataTable)GetValue(ResultsProperty); }
set { SetValue(ResultsProperty, value); }
}
}
I've tapped this out from memory, so apologies if the code isn't exactly right. The easiest way to declare a new dependency property is to use the propdp code snippet. It's a lot of syntax to memorize.

Resources