Viewmodel instantiates before it is needed - c#

I have a little problem with MVVM. Let me first sketch my problem.
I have a Parent View (DashboardConsultants) which has a datagrid. Each cell in that DataGrid has a tooltip, implemented like this:
<UserControl.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type vm:UC1001_AgreementDetailsViewModel}">
<v:UC1001_AgreementDetails_View />
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="DataGridCell.ToolTip">
<Setter.Value>
<vm:UC1001_AgreementDetailsViewModel />
</Setter.Value>
</Setter>
The tooltip calls up my ViewModel (AgreementDetailsViewModel), which has the following code:
public UC1001_ActiveAgreementContract AgreementDetailsContract { get; set; }
public int AgreementID { get; set; }
public UC1001_AgreementDetailsViewModel()
{
AgreementDetailsContract = new UC1001_ActiveAgreementContract();
this.Initialize();
}
private void Initialize()
{
GetRefData();
ShowAgreementDetailsView();
}
private void GetRefData()
{
UC1001_ActiveAgreementArguments args = new UC1001_ActiveAgreementArguments();
args.AgreementID = 3;
DefaultCacheProvider defaultCacheProvider = new DefaultCacheProvider();
if (!defaultCacheProvider.IsSet("AgrDet:" + args.AgreementID))
{
ConsultantServiceClient client = new ConsultantServiceClient();
AgreementDetailsContract = client.GetAgreementDetailsByAgreementID(args);
defaultCacheProvider.Set("AgrDet:" + args.AgreementID, AgreementDetailsContract, 5);
}
else
{
AgreementDetailsContract = (UC1001_ActiveAgreementContract)defaultCacheProvider.Get("AgrDet:" + args.AgreementID);
}
}
private void ShowAgreementDetailsView()
{
// Initialize
var regionManager = ServiceLocator.Current.GetInstance<IRegionManager>();
// Show content
var agreementDetailsWorkspace = new Uri("UC1001_AgreementDetails_View", UriKind.Relative);
regionManager.RequestNavigate("ContentRegion", agreementDetailsWorkspace);
}
The goal of the ViewModel is to get an Agreement from the database (currently a static one...) and then pass it on to the child View (UC1001_AgreementDetails_View) to show as the tooltip. The child View has the following constructor so the controls can bind to the contract:
public UC1001_AgreementDetails_View(ViewModels.UC1001_AgreementDetailsViewModel UC1001_AgreementDetailsViewModel)
{
InitializeComponent();
this.DataContext = UC1001_AgreementDetailsViewModel.AgreementDetailsContract;
}
I put a breakpoint on the ViewModel Initialize, and it fires when I get on the parent View, but it should fire when I get on the child View (thus when opening the tooltip in the datagrid). Does anyone know how I can fix this?
More information/code can be provided if needed.
EDIT:
I tried some stuff and I now I got something like this (which I feel is a little closer to the solution).
I changed my tooltip to the following (according to Rachels help):
<Setter Property="DataGridCell.ToolTip">
<Setter.Value>
<v:UC1001_AgreementDetails_View DataContext="{Binding AgreementDetailsViewModel}" />
</Setter.Value>
</Setter>
In my child view, I put the following binding
<Label Content="{Binding AgreementDetailsContract.Header}" Height="50" HorizontalAlignment="Left" Margin="8,6,0,0" Name="_labelHoofding" VerticalAlignment="Top" FontSize="22" />
Now my AgreementDetailsViewModel, which has a property called AgreementDetailsContract with all the information I want to show, is the DataContext of my child view. But my problem still exist. The AgreementDetailsViewModels fires on opening the ConsultantDashboard, when it should open on displaying the tooltip. Is there some kind of event/command I can put on the tooltip to fire the ViewModel? Also, I think something is wrong with the Binding of my label because it doesn't show information (altough it could be the same problem that the ViewModel doesn't pass the right information).
Again, if it looks a little complex to you, I will be happy to explain it further, or give more code if asked.
SOLVED:
I got the solution. I specify the binding in the constructor of the ChildView instead of its XAML or in the View Tooltip.
public UC1001_AgreementDetails_View()
{
InitializeComponent();
this.DataContext = new UC1001_AgreementDetailsViewModel();
}

It looks like your View is directly referencing the ViewModel, which means it will create a copy of your ViewModel when it starts up
This code
<Setter Property="DataGridCell.ToolTip">
<Setter.Value>
<vm:UC1001_AgreementDetailsViewModel />
</Setter.Value>
</Setter>
Should be
<Setter Property="DataGridCell.ToolTip">
<Setter.Value>
<!-- If you want to keep the DataTemplate, use a ContentControl -->
<v:UC1001_AgreementDetails_View DataContext="{Binding AgreementDetails}" />
</Setter.Value>
</Setter>
Your Data Structure should look something like this:
class MainViewModel
{
ObservableCollection<AgreementViewModel> Agreements;
}
class AgreementViewModel
{
// Loaded only when getter is called
AgreementDetailViewModel AgreementDetails;
}

Related

Changing to view from another view

i am currently learning C# and WPF. I have started coding simple application, i have
1 model class (Book),
2 model views (BookListModelView, AddNewBookModelView) and
2 views booth are UserControl (BookListView, AddNewBookView).
My question is how can i change to AddNewBookView when my current view is BookListView?
MainWindow.xaml :
<ContentControl Grid.Row="1" Grid.Column="1" Content="{Binding}"/>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
BookListModelView bookListMV = new BookListModelView();
this.DataContext = bookListMV;
}
}
BookListView.xaml.cs
public partial class BookListView : UserControl
{
public BookListView()
{
InitializeComponent();
}
private void NewBookBtn_Click(object sender, RoutedEventArgs e)
{
this.DataContext = new AddNewBookViewModel();
}
}
Thank you for your answers!
The usual way to achieve this is with DataTemplates. Basically you have a property in your main view model containing the view models of your child page:
public object PageViewModel {get; set;} // <--- needs property change notification
I've used object as the type here, whereas in practice you typically have some base class for your pages.
Over in your view you create a ContentControl and bind it to this property:
<ContentControl Content="{Binding PageViewModel}" />
Finally, you need to specify some kind of mapping specifying which view to display for each view model type. That's where data templates come in:
<DataTemplate DataType="{x:Type local:ChildViewModel1}">
<local:ChildViewControl1 />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ChildViewModel2}">
<local:ChildViewControl2 />
</DataTemplate>
Another way to achieve this is via Data Triggers. These get put in the parent's style and check for each possible value, setting the content explicitly:
<DataTrigger Binding="{Binding PageViewModelProperty}" Value="SomeChildViewModel1Value">
<Setter Property="Template" Value="{StaticResource MyPageTemplate1}" />
</DataTrigger>
<DataTrigger Binding="{Binding PageViewModelProperty}" Value="SomeChildViewModel2Value">
<Setter Property="Template" Value="{StaticResource MyPageTemplate2}" />
</DataTrigger>
... and so on. This is a better fit in cases where you may not have a separate class type for each of your page types e.g. in the case of page containing content that is generated dynamically.

Binding Button in DataGrid header to ViewModel

I have a DataGrid that is bound to an ICollectionView "Employees" in my ViewModel, which I want to filter for each column.
Here's what the XAML looks like:
<DataGrid ItemsSource="{Binding Employees}"
attachedBehaviors:DataGridColumnsBehavior.BindableColumns="{Binding EmployeeColumns}">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="16"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding}" Grid.Column="0"/>
<Button Content="v" Grid.Column="1" Click="ButtonClick"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.ColumnHeaderStyle>
</DataGrid>
This works fine and when calling ButtonClick I could pass the data context along to my ViewModel and search for this string. However, what I would prefer is to bind the button to an event in my ViewModel, so I can get a reference to the column from which this event originated and appropriately handle my data there.
My ViewModel looks like this:
class ViewModel : ChangeNotifier
{
public ICollectionView Employees { get; private set; }
public ViewModel()
{
var _employees = new List<Employee>{...
Employees = CollectionViewSource.GetDefaultView(_employees);
EmployeeColumns = new ObservableCollection<DataGridColumn>();
EmployeeColumns.Add(new DataGridTextColumn { Header = "First Name", Binding = new Binding("FirstName") });
EmployeeColumns.Add(new DataGridTextColumn { Header = "Last Name", Binding = new Binding("LastName") });
FilterMenu = new RelayCommand(new Action<object>(FilterContextMenu));
}
private ICommand filtermenu;
public ICommand FilterMenu
{
get
{
return filtermenu;
}
set
{
filtermenu = value;
}
}
public static void FilterContextMenu(object obj)
{
MessageBox.Show("Event Fired!");
}
public ObservableCollection<DataGridColumn> EmployeeColumns { get; private set; }
}
So my question is: How do I bind to the FilterContextMenu event?
I've tried:
<Button Content="v" Grid.Column="1" Command="{Binding FilterMenu}"/>
And also:
<Button Content="v" Grid.Column="1" Command="{Binding FilterMenu, RelativeSource= {RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"/>
None of which triggered the event.
Edit: I probably should add that the greater goal is to create a button that, when clicked, creates a dynamically populated context menu. I may be on the completely wrong track here.
I don't follow your description of the greater goal and dynamically populated context menu.
That sounds like it will have it's own set of somewhat more challenging problems getting a reference to any datacontext in the window. A contextmenu usually relies on placement target to get anything contextual because it isn't part of the window.
If you intend passing commands through from that then this could well get quite complicated.
But... that's a different question entirely.
Binding a button to the command which is in the datacontext of the datagrid.
My datagrid is called dg ( I'm a minimalist at heart ).
I got a bit wordy with my command and it's called ColHeaderCommand.
This works for me:
<DataGridTextColumn.Header>
<Button Content="Title" Command="{Binding DataContext.ColHeaderCommand, ElementName=dg}"/>
</DataGridTextColumn.Header>
My viewmodel is the datacontext of the window, which is inherited down to the datagrid.
You will want some sort of a parameter for that command to pass in what column or whatever you're doing whatever with.

How to set Button.Command from a ResourceDictionary?

I'm trying to implement a hamburger button by myself in a Windows 10 app. I'm running into a little trouble with my ResourceDictionary when trying to set the Command property of a Button (via a style). Here is my code:
Hamburger.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Octopie.Styles.Hamburger"
xmlns:local="using:Octopie.Styles">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Square.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style x:Key="HamburgerStyle" TargetType="Button" BasedOn="{StaticResource SquareStyle}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Command" Value="{Binding OnClicked}"/> <!--This is the part that's having issues-->
<Setter Property="Content" Value=""/>
<Setter Property="FontFamily" Value="Segoe MDL2 Assets"/>
</Style>
</ResourceDictionary>
Hamburger.xaml.cs
namespace Octopie.Styles
{
public sealed partial class Hamburger : ResourceDictionary
{
public Hamburger()
{
this.InitializeComponent();
}
public ICommand OnClicked => new ClickedCommand();
private class ClickedCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) =>
parameter is Button;
public void Execute(object parameter)
{
var button = (Button)parameter;
// Walk up the tree until we reach a SplitView
FrameworkElement parent = button;
do
parent = parent.Parent as FrameworkElement;
while (!(parent is SplitView));
var splitView = (SplitView)parent;
splitView.IsPaneOpen = !splitView.IsPaneOpen;
}
}
}
}
For some reason the binding for the Command property doesn't seem to be working; when I set a breakpoint inside the Execute method and click the button, the breakpoint is never hit. I tried adding a DataContext="{Binding RelativeSource={RelativeSource Self}}" to the top of the XAML file, but for some reason ResourceDictionary doesn't seem to support DataContext.
tl;dr: What can I do to make the Button.Command property bind correctly to OnClicked within the setter?
Like Mike said, usually we won't set Button.Command in ResourceDictionary. A hamburger button may not only be in SplitView but can be in another place and then you may need bind another command. So you can refer to Mike's suggestion.
But if you do want to set it in ResourceDictionary, you can try like following:
Firstly, in your case, your command is fixed, you can declare your ClickedCommand as a public class, then in the Style,set the Command like:
<Setter Property="Command">
<Setter.Value>
<local:ClickedCommand />
</Setter.Value>
</Setter>
After this, you can use your command, but this won't fix your problem as in ClickedCommand, you use parameter to retrieve the Button, but the parameter is not the "sender" of the Command, but the object passed with CommandParameter property. So we need set this in the Style.
However, Bindings in Style Setters are not supported in UWP Apps. See Remarks in Setter class:
The Windows Runtime doesn't support a Binding usage for Setter.Value (the Binding won't evaluate and the Setter has no effect, you won't get errors, but you won't get the desired result either).
A workaround for this is using attached property to set up the binding in code behind for you. For example:
public class BindingHelper
{
public static readonly DependencyProperty CommandParameterBindingProperty =
DependencyProperty.RegisterAttached(
"CommandParameterBinding", typeof(bool), typeof(BindingHelper),
new PropertyMetadata(null, CommandParameterBindingPropertyChanged));
public static bool GetCommandParameterBinding(DependencyObject obj)
{
return (bool)obj.GetValue(CommandParameterBindingProperty);
}
public static void SetCommandParameterBinding(DependencyObject obj, bool value)
{
obj.SetValue(CommandParameterBindingProperty, value);
}
private static void CommandParameterBindingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
BindingOperations.SetBinding(d, Button.CommandParameterProperty, new Binding { RelativeSource = new RelativeSource() { Mode = RelativeSourceMode.Self } });
}
}
}
Then in Style, using
<Setter Property="local:BindingHelper.CommandParameterBinding" Value="True" />
will set the Button as CommandParameter. Your Hamburger.xaml may like:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Octopie.Styles">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Square.xaml" />
</ResourceDictionary.MergedDictionaries>
<Style x:Key="HamburgerStyle" TargetType="Button" BasedOn="{StaticResource SquareStyle}">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Command">
<Setter.Value>
<local:ClickedCommand />
</Setter.Value>
</Setter>
<Setter Property="local:BindingHelper.CommandParameterBinding" Value="True" />
<Setter Property="Content" Value="" />
<Setter Property="FontFamily" Value="Segoe MDL2 Assets" />
</Style>
</ResourceDictionary>
I delete x:Class="Octopie.Styles.Hamburger" and Hamburger.xaml.cs as there is no need to use code-behind for your ResourceDictionary.
Now we can use this ResourceDictionary in our page like:
<Page.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Hamburger.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<SplitView DisplayMode="CompactOverlay" IsPaneOpen="True">
<SplitView.Pane>
<StackPanel>
<Button Style="{StaticResource HamburgerStyle}" />
</StackPanel>
</SplitView.Pane>
</SplitView>
</Grid>
But there is another problem in Execute method of ClickedCommand. In this method, you've used FrameworkElement.Parent to retrieve the SplitView. But
Parent can be null if an object was instantiated, but is not
attached to an object that eventually connects to a page object root.
Most of the time, Parent is the same value as returned by
VisualTreeHelper APIs. However, there may be cases where Parent
reports a different parent than VisualTreeHelper does.
And in your case, you need use VisualTreeHelper.GetParent to get the SplitView. We can use a helper method to do this:
public static T FindParent<T>(DependencyObject child) where T : DependencyObject
{
//get parent item
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
T parent = parentObject as T;
if (parent != null)
return parent;
else
return FindParent<T>(parentObject);
}
Then in Execute method using:
public void Execute(object parameter)
{
var button = (Button)parameter;
var splitView = FindParent<SplitView>(button);
splitView.IsPaneOpen = !splitView.IsPaneOpen;
}
Now the HamburgerStyle will work as you want.
What the hell?
You're going about this all wrong. You don't need to declare a new ICommand in a ResourceDictionary, it simply doesn't belong there. It belongs in your View Model, or whatever the Button.DataContext is set to.
The purpose of a Style is to control the look and feel of your controls, they should not explicitly set their own behaviours (commands).
Let me show you an example. You should declare your button like this:
<Button Style="{StaticResource HamburgerStyle}" Command="{Binding ClickedCommand}"/>
Where ClickedCommand is an object in your View Model.
Your HamburgerStyle should not set it's own Command property, otherwise you are limiting your Button to one single implementation of ICommand, this is unwise.

WPF Customcontrol, Templates, Inheritance and Dependencyproperties

I've got some troubles with a custom control I need to create. I try to explain you my needs first
I need to have a combobox that permits to check more than one item at time (with checkbox) but I want it to be smart enought to bind to a specific type.
I've found some MultiSelectionComboBox but none reflects my need.
Btw my main problem is that I wish to have a generic class as
public class BaseClass<T> : BaseClass
{
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable<T>), typeof(BaseClass<T>), new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(BaseClass<T>.OnItemsSourceChanged)));
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
int i = 0;
//MultiSelectComboBox control = (MultiSelectComboBox)d;
//control.DisplayInControl();
}
public IEnumerable<T> ItemsSource
{
get { return (IEnumerable<T>)GetValue(ItemsSourceProperty); }
set
{
SetValue(ItemsSourceProperty, value);
}
}
}
public class BaseClass : Control
{
}
and a more context specific item for example
public class MultiCurr : BaseClass<Currency>
{
static MultiCurr()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiCurr), new FrameworkPropertyMetadata(typeof(MultiCurr)));
}
}
In my App.xaml I've defined a resource as
<ResourceDictionary>
<Style TargetType="local:MultiCurr">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MultiCurr">
<ComboBox Width="120" Background="Red" Height="30" ItemsSource="{Binding ItemsSource}" DisplayMemberPath="Description" ></ComboBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
In my MainWindow I've created an object as
<Grid>
<local:MultiCurr x:Name="test" ItemsSource="{Binding Currencies}"></local:MultiCurr>
</Grid>
and the MainWindow.cs is defined as
public partial class MainWindow : Window, INotifyPropertyChanged
{
private IList currencies;
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
this.Loaded += MainWindow_Loaded;
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var lst = new List<Currency>();
for (int i = 0; i < 10; i++)
{
var curr = new Currency
{
ID = i,
Description = string.Format("Currency_{0}", i)
};
lst.Add(curr);
}
Currencies = lst;
}
public IList<Currency> Currencies
{
get
{
return this.currencies;
}
set
{
this.currencies = value;
NotifyPropertyChanged("Currencies");
}
}
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And here's the result ...
I was wondering what am I doing wrong? is it possible what am I tring to achieve?
Thanks
UPDATE #1:
I've seen that the main problem is the datacontext of the custom usercontrol
<Application.Resources>
<ResourceDictionary>
<Style TargetType="local:MultiCurr">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MultiCurr">
<ComboBox Width="120" Background="Red" Height="30" ItemsSource="{Binding **Currencies**}" DisplayMemberPath="{Binding **DisplayMemeberPath**}" ></ComboBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Application.Resources>
If I put ItemsSource as Currency (which is a property of the MainWindow) it shows.
If I put ItemsSource and DisplayMemberPath (which are defined in the BaseClass no.. how can I set the context of the usercontrol to itself?)
UPDATE #2
I've added a GoogleDrive link to the project here if anyone wants to try the solution
Thanks
Combobox is not suitable control for multiselection, because it has given behaviour, that when yo select item, Combobox closes itself. That's why Combobox doest not have SelectionMode property like ListBox. I think that ListBox inside expander is what you need.
Generic Types are not a way to go. WPF handles this different, better way. Take listbox as an example. If you bind listbox.itemssource to generic observable collection, and you try to define e.g ItemTemplate, you get full intellisense when writing bindings and warning if you bind to not existing property. http://visualstudiomagazine.com/articles/2014/03/01/~/media/ECG/visualstudiomagazine/Images/2014/03/Figure8.ashx WPF designer automatically recognizes type parameter of your observable collection. Of cousre you need to specify type of datacontext in your page by using something like this: d:DataContext="{d:DesignInstance search:AdvancedSearchPageViewModel}". However your control dont have to be and shouldn't be aware of type of items.
Following example demonstrates control that meets your requirements:
<Expander>
<Expander.Header>
<ItemsControl ItemsSource="{Binding ElementName=PART_ListBox, Path=SelectedItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding Mode=OneWay}" />
<Run Text=";" />
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Expander.Header>
<Expander.Content>
<ListBox x:Name="PART_ListBox" SelectionMode="Multiple">
<ListBox.ItemsSource>
<x:Array Type="system:String">
<system:String>ABC</system:String>
<system:String>DEF</system:String>
<system:String>GHI</system:String>
<system:String>JKL</system:String>
</x:Array>
</ListBox.ItemsSource>
</ListBox>
</Expander.Content>
</Expander>
I reccomend you to create control derived from ListBox (not usercontrol).
I have hardcoded datatemplates, but you should expose them in your custom dependency properties and use TemplateBinding in you control template. Of course you need to modify expander so it looks like combobox and ListBoxItem style so it looks like CheckBox, but it is ease.

Bind a property in xaml template to viewmodel (RibbonTextBox)

Background info of larger problem
The problem I am trying to solve is to allow a user to set the MinWidth of the label inside of the RibbonTextBox control template. I intend to the same with other properties once I can figure out the first one. The aim of this is to be able to align RibbonTextBoxes stacked on top of each other by setting widths. I am so far solved my problem by hardcoding the values in the control template. I would like to make this control reusable and thus need to be able to set up some binding.
The problem that needs solving
I have the following xaml (lots of xaml has been removed for readability). At the centre of this xaml you can see a label. That label has a MinWidth property which is the focus of my question.
<DataTemplate x:Uid="DataTemplate_0" DataType="{x:Type element:RibbonTextBoxVM}">
<ribbon:RibbonTextBox x:Uid="ribbon:RibbonTextBox_1" IsReadOnly="{Binding IsReadOnly}" Text="{Binding Text}" Label="{Binding Label}" >
<ribbon:RibbonTextBox.Style>
<Style TargetType="{x:Type ribbon:RibbonTextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ribbon:RibbonTextBox}">
<StackPanel Orientation="Horizontal">
<Label Margin='2,0,0,0' Padding='0,0,0,5' BorderThickness='0,0,0,0' HorizontalAlignment='Stretch' VerticalAlignment='Bottom'
HorizontalContentAlignment='Left' VerticalContentAlignment='Top' Background='#00FFFFFF' FlowDirection='LeftToRight'
Visibility='Visible' MinWidth="80">
<!--other stuff-->
</Label>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ribbon:RibbonTextBox.Style>
</ribbon:RibbonTextBox>
</DataTemplate>
The following is the viewmodel that backs the above xaml.
public class RibbonTextBoxVM : ViewModel
{
public string Label
{
get { return GetValue(Properties.Label); }
set { SetValue(Properties.Label, value); }
}
public string Text
{
get { return GetValue(Properties.Text); }
set { SetValue(Properties.Text, value); }
}
public bool IsReadOnly
{
get { return GetValue(Properties.IsReadOnly); }
set { SetValue(Properties.IsReadOnly, value); }
}
public RibbonTextBoxVM(string text, string label, bool isReadOnly)
{
Text = text;
Label = label;
IsReadOnly = isReadOnly;
}
}
What I would like to do is have a property LabelMinWidth.
public double LabelMinWidth
{
get { return GetValue(Properties.LabelMinWidth); }
set { SetValue(Properties.LabelMinWidth, value); }
}
I want to allow the user to pass in a value to the constructor to set that property. That is the easy part.
The part I cannot figure out is how to bind my new LabelMinWidth to the MinWidth property of the label inside the control template in the xaml.
If anyone can point me in the right direction that would be great. Ill be happy to answer any questions about the problem.
Since your In your RibbonTextBox has your VM as its DataContext, you can use a Bindingin your ControlTemplate, just like you bound the other properties:
<Label ... MinWidth="{Binding LabelMinWidth}">
This works because in WPF, the DataContext inherits to all children (unless overridden). So if you have a property on your VM that you want to bind to in a control in a template, you just bind to it.

Categories

Resources