How can you set the DataContext dynamically in C#-code? - wpf

I builded a RibbonGroupBox like this in a C# file:
public class TextControl : RibbonGroupBox
{
public TextControl()
{
const double widthOfComboBoxes = 150;
Binding fontsBinding = new Binding();
fontsBinding.Source = (TextControlVM)DataContext;
fontsBinding.Path = new System.Windows.PropertyPath("Fonts");
fontsBinding.Mode = BindingMode.TwoWay;
Binding fontSizeBinding = new Binding();
fontSizeBinding.Source = (TextControlVM)DataContext;
fontSizeBinding.Path = new System.Windows.PropertyPath("FontSize");
fontSizeBinding.Mode = BindingMode.TwoWay;
/* Combobox for the fonts (Arial, etc.) */
Fluent.ComboBox fontCombo = new Fluent.ComboBox();
fontCombo.SetBinding(Fluent.ComboBox.ItemsSourceProperty, fontsBinding);
fontCombo.SelectedItem = ((TextControlVM)DataContext).DefaultFont;
fontCombo.Width = widthOfComboBoxes;
this.AddChild(fontCombo);
/* Combobox for the fontsizes */
Fluent.ComboBox fontSizeCombo = new Fluent.ComboBox();
fontSizeCombo.SetBinding(Fluent.ComboBox.ItemsSourceProperty, fontSizeBinding);
fontSizeCombo.SelectedItem = ((TextControlVM)DataContext).DefaultFontSize;
fontSizeCombo.Width = widthOfComboBoxes;
this.AddChild(fontSizeCombo);
}
}
I furthermore have a viewmodel (TextControlVM) that contains Properties for Fonts, FontSize, DefaultFont and DefaultFontSize.
When I now use this in another module like this, the DataContext in the above example is null:
<Fluent:RibbonTabItem Header="Export">
<TextControl DataContext="{Binding DataContext.TextControl}"/>
</Fluent:RibbonTabItem>
When I build the RibbonGroupBox with XAML code everything works fine, so I want to do what XAML automatically does. How can I do that?
Background: I want to use the RibbonGroupBox in several modules. That is why I build it with C#-Code, so that I can access it dynamically. The DataContext will change dependend on the call.

The DataContext is implied in a binding automatically, so you are essentially binding to RibbonTabItem.DataContext.DataContext.TextControl, which doesn't exist
To bind to RibbonTabItem.DataContext.TextControl, simply leave the extra DataContext out of the binding
<Fluent:RibbonTabItem Header="Export">
<TextControl DataContext="{Binding TextControl}"/>
</Fluent:RibbonTabItem>

Related

WPF: Binding DataGrid to collection programmatically

I already spent hours on this, and similar topics did not help. :(
I've got an object of type "Chart" which contains a List "LineItems".
I want to bind LineItems programmatically to a DataGrid in an UserControl.
Usercontrol XAML:
<DataGrid Name="myData" AutoGenerateColumns="True">
Usercontrol Code behind:
public void SetItemSource(ChartingBase.Chart chart)
{
//DataGrid.ItemsSource = chart.LineItems; // working!
// this is not working:
this.DataContext = chart;
Binding b = new Binding( "LineItems" );
b.Mode = BindingMode.TwoWay;
b.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
myData.SetBinding( DataGrid.ItemsSourceProperty, b );
}
Setting just the ItemsSource works. Creating the binding manually does not work and I have no clue what else I could try. Thanks!
Try
BindingOperations.SetBinding(myData, DataGrid.ItemsSourceProperty, new Binding("LineItems") { Source = chart });
In WPF, it is customary to put your data into an ObservableCollection<T> collection and data bind that to the DataGrid.ItemsSource property. Then you can fill or manipulate the collection in code and the UI will update automatically. Try this:
<DataGrid Name="myData" ItemsSource="{Binding Items}" AutoGenerateColumns="True">
...
public void SetItemSource(ChartingBase.Chart chart)
{
this.DataContext = chart;
Items = new ObservableCollection<YourDataType>();
foreach (SomeDataType dataType in chart.SomeCollection)
{
Items.Add(new YourDataType(dataType.SomeProperty, ...));
}
}

WPF/AvalonDock v2.0 LayoutDocument

How can I add programmatically LayoutDocument with some of UIElements inside it? (like stackpanel, scrollviewer etc.) I'd like to add new LayoutDocument with stackpanel, canvas etc. to LayoutDocumentPane when user clicks "New project" button. May I somehow clone xaml code from one LayoutDocument and load it's to new one? And is it possible to bind Title LayoutDocument property to ViewModel Property? ( i get error it has to be dependency property )
You can use Content property. For example if you want to add a new LayoutDocument with a custom content (StackPanel e.g.) you could do it as follow:
//Get the main LayoutDocumentPane of your DockingManager
var documentPane = dockManager.Layout.Descendents().OfType<LayoutDocumentPane>().FirstOrDefault();
if (documentPane != null)
{
LayoutDocument layoutDocument = new LayoutDocument {Title = "New Document"};
//*********Here you could add whatever you want***********
layoutDocument.Content = new StackPanel();
//Add the new LayoutDocument to the existing array
documentPane.Children.Add(layoutDocument);
}
First, in XAML - give the name to the Grid, for example, x:Name = "mainGrid"
Then in class write this
//Create button - we put this in document
Button mybutton = new Button();
mybutton.Content = "hello";
mybutton.Width = 100;
mybutton.Height = 50;
mybutton.Click += (sender, ev) => { MessageBox.Show("Hello"); };
DockingManager dockmanager = new DockingManager();
//Set theme
dockmanager.Theme = new Xceed.Wpf.AvalonDock.Themes.ExpressionLightTheme();
//Create LayoutRoot
var layoutroot = new Xceed.Wpf.AvalonDock.Layout.LayoutRoot();
//Create LayoutPanel
var layoutpanel = new Xceed.Wpf.AvalonDock.Layout.LayoutPanel();
//Create LayoutDocumentPane
var layoutdocpane = new Xceed.Wpf.AvalonDock.Layout.LayoutDocumentPane();
//Create LayoutDocument and set parameters of Document
var LayoutDocument = new Xceed.Wpf.AvalonDock.Layout.LayoutDocument();
LayoutDocument.Title = "Some text";
//Put button in Document
LayoutDocument.Content = mybutton;
layoutdocpane.Children.Add(LayoutDocument);
layoutpanel.Children.Add(layoutdocpane);
layoutroot.RootPanel.Children.Add(layoutpanel);
dockmanager.Layout = layoutroot;
mainGrid.Children.Add(dockmanager);
Sorry for my poor English. Please rewrite this, if it would be helpful.
I'm not that familiar with WPF and especially AvalonDock. I did it like this and it works so far :)
You can write a separate class for your documents that inherits from LayoutDocument. In that way you should be able to edit the standard layout of your "Project-Document" with the VisualStudio Designer (add your stackpanel, canvas etc.).
(I assume that you have a standard way of displaying your "Project-Document". Otherwise you could build the content yourself in code behind like you would do in WPF and put it inside the LayoutDocument.)
For example:
<ad:LayoutDocument x:Class="Namespace.MyDocument"
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"
xmlns:ad="http://avalondock.codeplex.com"
d:DesignHeight="500"
d:DesignWidth="800"
<Grid>
<!-- content -->
</Grid>
</ad:LayoutDocument>
And the class in code behind that inherits from LayoutDocument:
namespace Namespace
{
public partial class MyDocument : AvalonDock.Layout.LayoutDocument
{
// ...
}
}
To create and add a new document you just instantiate a new MyDocument object and add it to the collection via binding or something like layoutDocumentPane.Children.Add(doc).
I don't know about the binding for the Title Property, though.
Thats exactly right.
You can add the title by just adding doc.Title = "My document title"
or
You can add Title="My document title" in the document.xaml which is going to be the child.

Databinding - In XAML, how to databind to a property of the control created dynamically?

The control I created dynamically is a radiobutton, and I am trying to control the visibility of a hyperlinkbutton according to the IsChecked property of the radiobutton created in code-behind.
In my XAML file:
<HyperlinkButton Visibility="{Binding IsChecked, ElementName=tempRadio, Converter={StaticResource visibilityConvert}}" Content="Insert Record" Click="addRecord" Background="Aqua" Foreground="White"></HyperlinkButton>
Apparently I don't think I should use ElementName in this case, since it is only for controls created in XAML.
In my C# file:
public RadioButton tempRadio;
...
I would start with this:
first set the binding target on your hyperlink
hyperlinkButton.BindingTarget = tempRadio.IsChecked;
then set the binding:
hyperlinkButton.SetBinding(hyperlinkButton.BindingTarget, CreateValueBinding());
private Binding CreateValueBinding()
{
var valueBinding = new Binding();
valueBinding.Mode = BindingMode.TwoWay;
valueBinding.NotifyOnValidationError = true;
valueBinding.ValidatesOnExceptions = true;
valueBinding.UpdateSourceTrigger = UpdateSourceTrigger.Explicit;
valueBinding.Path = new PropertyPath(this.DataMemberBinding.Path.Path);
return valueBinding;
}

WPF DataGrid binding to DataTable in XAML

I'm completely new to WPF/XAML. I'm trying to work out XAML code to bind a DataTable to DataGrid. What I have is an instance of custom DataContainer class which implements INotifyPropertyChanged. This class has a property:
private DataTable totalsStatus = new DataTable();
public DataTable TotalsStatus
{
get { return totalsStatus; }
set
{
totalsStatus = value;
NotifyPropertyChanged("TotalsStatus");
}
}
now, in the C'tor of my MainWindow I have this, which works like a charm:
Binding b = new Binding();
b.Source = DataContainer;
b.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
b.Path = new PropertyPath("TotalsStatus");
DataGridMain.SetBinding(DataGrid.ItemsSourceProperty, b);
How do I make this binding in XAML?
You need to use an objectdataprovider.
<ObjectDataProvider x:Key="yourdataproviderclass"
ObjectType="{x:Type local:yourdataproviderclass}" />
<ObjectDataProvider x:Key="dtable"
ObjectInstance="{StaticResource yourdataproviderclass}"
MethodName="GetTable"/> <!--here would be the method that returns your datasource-->
Then you can bind it to your datagrid in XAML with
<DataGrid ItemsSource="{Binding Source={StaticResource dtable}}" ></DataGrid>
There are different ways to do bindings in xaml though, so play around with it a bit.

DataTemplate + MVVM

I'm using MVVM and each View maps to a ViewModel with a convention. IE
MyApp.Views.MainWindowView
MyApp.ViewModels.MainWindowViewModel
Is there a way to remove the DataTemplate and do it in C#? with some sort of loop?
<DataTemplate DataType="{x:Type vm:MainWindowViewModel}">
<vw:MainWindowView />
</DataTemplate>
So basically, you need to create data templates programmatically... That's not very straightforward, but I think you can achieve that with the FrameworkElementFactory class :
public void AddDataTemplateForView(Type viewType)
{
string viewModelTypeName = viewType.FullName + "Model";
Type viewModelType = Assembly.GetExecutingAssembly().GetType(viewModelTypeName);
DataTemplate template = new DataTemplate
{
DataType = viewModelType,
VisualTree = new FrameworkElementFactory(viewType)
};
this.Resources.Add(viewModelType, template);
}
I didn't test it, so a few adjustments might be necessary... For instance I'm not sure what the type of the resource key should be, since it is usually set implicitly when you set the DataType in XAML
Thanks Thomas, using your code i've done this.
You need to use the DataTemplateKey when adding the resoures :D
private void AddAllResources()
{
Type[] viewModelTypes = Assembly.GetAssembly(typeof(MainWindowViewModel)).GetTypes()
.Where(t => t.Namespace == "MyApp.ViewModels" && t.Name.EndsWith("ViewModel")).ToArray();
string viewName = null;
string viewFullName = null;
foreach (var vmt in viewModelTypes)
{
viewName = vmt.Name.Replace("ViewModel", "View");
viewFullName = String.Format("MyApp.Views.{0}, MyApp", viewName);
DataTemplate template = new DataTemplate
{
DataType = vmt,
VisualTree = new FrameworkElementFactory(Type.GetType(viewFullName, true))
};
this.Resources.Add(new DataTemplateKey(vmt), template);
}
}

Resources