WPF user controls and name scoping - c#

I've been playing around with WPF and MVVM and noticed a strange thing. When using {Binding ElementName=...} on a custom user control, the name of the root element within the user control seems to be visible in the window using the control. Say, here is an example user control:
<UserControl x:Class="TryWPF.EmployeeControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TryWPF"
Name="root">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding}"/>
<Button Grid.Column="1" Content="Delete"
Command="{Binding DeleteEmployee, ElementName=root}"
CommandParameter="{Binding}"/>
</Grid>
</UserControl>
Looks pretty legit to me. Now, the dependency property DeleteEmployee is defined in the code-behind, like this:
public partial class EmployeeControl : UserControl
{
public static DependencyProperty DeleteEmployeeProperty
= DependencyProperty.Register("DeleteEmployee",
typeof(ICommand),
typeof(EmployeeControl));
public EmployeeControl()
{
InitializeComponent();
}
public ICommand DeleteEmployee
{
get
{
return (ICommand)GetValue(DeleteEmployeeProperty);
}
set
{
SetValue(DeleteEmployeeProperty, value);
}
}
}
Nothing mysterious here. Then, the window using the control looks like this:
<Window x:Class="TryWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TryWPF"
Name="root"
Title="Try WPF!" Height="350" Width="525">
<StackPanel>
<ListBox ItemsSource="{Binding Employees}" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<local:EmployeeControl
HorizontalAlignment="Stretch"
DeleteEmployee="{Binding DataContext.DeleteEmployee, ElementName=root}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
Again, nothing fancy... except the fact that both the window and the user control have the same name! But I'd expect root to mean the same thing throughout the whole window XAML file, and therefore refer to the window, not to the user control. Alas, the following message is printed when I run it:
System.Windows.Data Error: 40 : BindingExpression path error:
'DeleteEmployee' property not found on 'object' ''String'
(HashCode=-843597893)'.
BindingExpression:Path=DataContext.DeleteEmployee;
DataItem='EmployeeControl' (Name='root'); target element is
'EmployeeControl' (Name='root'); target property is 'DeleteEmployee'
(type 'ICommand')
DataItem='EmployeeControl' (Name='root') makes me think that it treats ElementName=root as referring to the control itself. The fact that it looks for DeleteEmployee on string confirms that suspicion because string is exactly what the data context is in my contrived VM. Here it is, for the sake of completeness:
class ViewModel
{
public ObservableCollection<string> Employees { get; private set; }
public ICommand DeleteEmployee { get; private set; }
public ViewModel()
{
Employees = new ObservableCollection<string>();
Employees.Add("e1");
Employees.Add("e2");
Employees.Add("e3");
DeleteEmployee = new DelegateCommand<string>(OnDeleteEmployee);
}
private void OnDeleteEmployee(string employee)
{
Employees.Remove(employee);
}
}
It is instantiated and assigned to the window in the constructor, which is the only thing in code-behind for the window:
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
This phenomenon prompts the following questions:
Is this by design?
If so, how is someone using a custom control supposed to know what name it uses internally?
If Name is not supposed to be used in custom control at all?
If so, then what are the alternatives? I switched to using {RelativeSource} in FindAncestor mode, which is working fine, but are there better ways?
Does this have anything to do with the fact that data templates define their own names copes? It doesn't stop me from referring to the main window from within a template if I just rename it so the name doesn't clash with the control.

Your confusion here about how wpf namescopes work is understanable in this situation.
Your issue is simply that you are applying a binding upon a UserControl, which is the "root" (so to speak) of its own namescope. UserControls, and pretty much any container objects, have their own namescopes. These scopes encompass not only child elements, but the object that contains the namescope as well. This is why you can apply x:Name="root" to your window and (except in this one case) locate it from a child control. If you couldn't, namescopes would be pretty much useless.
The confusion comes when you're acting upon a root of a namescope within an encompassing namescope. Your assumption was that the parent's namescope had precedence, but it does not. The Binding is calling FindName on the target object, which in your case is your user control. (Side note, the Binding isn't doing jack, the actual calls can be found in ElementObjectRef.GetObject, but that's where the Binding delegates the call to)
When you call FindName on the root of a namescope, only names defined within this scope are examined. Parent scopes are not searched. (Edit... a bit more reading of the source http://referencesource.microsoft.com/#PresentationFramework/src/Framework/MS/Internal/Data/ObjectRef.cs,5a01adbbb94284c0 starting at line 46 I see that the algorithm walks up the visual tree until it finds a target, so child scopes have precedence over parent scopes)
The result of all this is that you get the user control instance instead of the window, like you were hoping. Now, to answer your individual questions...
1. Is this by design?
Yep. Otherwise namescopes wouldn't work.
2. If so, how is someone using a custom control supposed to know what name it uses internally?
Ideally, you wouldn't. Just like you don't ever want to have to know the name of the root of a TextBox. Interestingly, though, knowing the names of templates defined within a control is often important when attempting to modify it's look and feel...
3. If Name is not supposed to be used in custom control at all?
If so, then what are the alternatives? I switched to using {RelativeSource} in FindAncestor mode, which is working fine, but are there better ways?
No! It's fine. Use it. If you aren't sharing your UserControl with other people, just make sure to change its name if you are experiencing this particular problem. If you aren't having any problem, reuse the same name all day, it isn't hurting anything.
If you ARE sharing your UserControl, you should probably rename it to something that won't conflict with other people's names. Call it MuhUserControlTypeName_MuhRoot_Durr or something.
4. If so, then what are the alternatives? I switched to using {RelativeSource} in FindAncestor mode, which is working fine, but are there better ways?
Nah. Just change the x:Name of your user control and move on.
5. Does this have anything to do with the fact that data templates define their own names copes? It doesn't stop me from referring to the main window from within a template if I just rename it so the name doesn't clash with the control.
No, I don't believe so. I don't think there is any good reason for it to be, anyhow.

Related

DependencyProperty default value in Visual Studio designer

How do I provide a default value to a binding in a UserControl's XAML, in such a way that Visual Studio will correctly pick up on it?
I'm trying to implement a reusable WPF UserControl, which uses DependencyProperties to customize its behavior, and I'm running into strange behavior in Visual Studio regarding the property's default value.
Take the following control:
Widget.designer.cs
namespace WpfPropertiesTest
{
public partial class Widget : UserControl
{
public Widget()
{
InitializeComponent();
}
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register(nameof(Test), typeof(string), typeof(Widget),
new PropertyMetadata("Hello World"));
public string Test
{
get { return (string)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
}
}
Widget.xaml:
<UserControl x:Class="WpfPropertiesTest.Widget"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="_this">
<TextBlock Text="{Binding ElementName=_this, Path=Test, FallbackValue='Fallback', TargetNullValue='Null', Mode=OneWay}" />
</UserControl>
When I look at the designer of Widget.xaml, I see the fallback value, which is reasonable:
However, lets say I now add a Widget to a window:
// TestWindow.xaml
<Window x:Class="WpfPropertiesTest.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfPropertiesTest"
Title="TestWindow" Height="100" Width="200">
<Grid>
<local:Widget />
</Grid>
</Window>
I would expect to see one of either "Hello World", "Fallback", or "Null", but what I actually get is this:
... Which is the name of the property that I'm binding to.
At runtime, using the same code, I can see the default value just fine:
Now, if I explicitly set a value for the property:
<local:Widget Test="A value that I put in." />
The designer now correctly shows:
All of this tells me that my binding is correct, but the Visual Studio (reasonably) doesn't actually look at the dependency properties in the codebehind. This becomes a problem when I'm actually doing something with the property, such as binding visibility to it.
So, what am I missing? Is there some way to give Visual Studio a value to use in the designer? I don't want to have to explicitly set all of my UserControl properties every time I use it, and I would like to have the control behave in the designer as close to runtime as possible.
Should I forego the binding altogether, and just set the value in the codebehind in the property callback?
In a more general sense, is this the correct approach for making reusable UserControls that can be configured by the consumer?
Solution
If I'm not mistaken the reason for that behavior is that you have project code disabled in your designer. To enable project code toggle the rightmost button in the bottom part of the designer, right to the left from the scroll bar.
Diagnosis
Let's do a simple experiment. Firstly, let's create simple test class:
namespace Test
{
public class EmptyClass { }
}
Then let's put this XAML code in some XAML control with a designer:
<Grid xmlns:test="clr-namespace:Test">
<Grid.Resources>
<ObjectDataProvider x:Key="Provider"
ObjectType="test:EmptyClass"
MethodName="GetType" />
</Grid.Resources>
<TextBlock Text="{Binding Source={StaticResource Provider}}" />
</Grid>
Now in the designer view, when project code is enabled, we see Test.EmptyClass. However, when project code is disabled, we see something along the lines of Mocks.Test_EmptyClass_0_96752088. This leads to a conclusion that the designer uses a generated mock class rather than the actual class (which is what I'd expect - that the designer is not using code from my project).
I don't know the exact mechanics of generating mock classes. Perhaps the static constructor for the mocked class is not called, or the mock class does not even derive from the original class. Either way, your code, which assigns the default property metadata, is not (and should not be) executed.

WinRT DependencyProperty is never set

I have some issues with my DependencyProperty in a custom UserControl.
I need to display informations about people in a particular way. To achieve this, I have several UserControls that receive a List<PeopleList> which contains (obviously) one or more People.
Let me show you my (simplified) code and I'll then explain to you the actual behavior of my app.
Here is my UserControl :
public abstract class PeopleLine : UserControl
{
public static readonly DependencyProperty PeopleListProperty =
DependencyProperty.Register("PeopleList", typeof(List<PeopleModel>), typeof(PeopleLine), new PropertyMetadata(default(List<PeopleModel>)));
public List<PeopleModel> PeopleList
{
get { return (List<PeopleModel>)GetValue(PeopleListProperty); }
set { SetValue(PeopleListProperty, value); }
}
}
Then my xaml :
<local:PeopleLine
x:Class="MyApp.Controls.EventSheet.OnePeople"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyApp.Controls.EventSheet"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid
Margin="0 5"
VerticalAlignment="Top"
Height="51">
<TextBlock
Grid.Column="1"
HorizontalAlignment="Center"
Foreground="Red"
FontSize="25"
Text="{Binding PeopleList[0].Name}"/>
</Grid>
</local:PeopleLine>
And this all starts with my Page which contains an ItemsControl with a correct ItemsSource (I already checked it) and an ItemTemplateSelector (also working perfectly). Here is one of the DataTemplate used by the selector :
<DataTemplate x:Key="OnePeople">
<peoplecontrols:OnePeople
PeopleList="{Binding LinePeopleList}"/>
</DataTemplate>
I'm using several Models That are not really important here since I simplified my code to only have the most important information.
So, back to my issue. When replacing the peoplecontrols:OnePeople in the selector's DataTemplate by a string and putting LinePeopleList[0].Nameas Text, I have the correct text displayed, proving me that my data is correct at this point.
Problem is that when putting back my peoplecontrols:OnePeople, my DependencyProperty is never set. I put a breakpoint at PeopleList's setter and it never triggers.
I tried several modifications (especially those that are given in this post, so replacing the typeof(List<PeopleModel>)by typeof(object) has already been tried) with no success. Also, I tried to replace my DependencyProperty to a string and directly send the name in the DataTemplate but the setter is still not called...
I have no more ideas now and don't understand what's wrong with my code. Any help would be greatly appreciated.
Thanks in advance.
Thomas
Try adding the following line in your UserControl's Constructor, after the call to InitializeComponent:
(this.Content as FrameworkElement).DataContext = this;
I created a sample app on regarding this. Hopefully it reflects your situation correctly:
https://github.com/mikoskinen/uwpusercontrollistdp
If you clone the app and run it, you'll notice that the binding doesn't work. But if you uncomment the Datacontext = this line from UserControl, everything should work OK. Here's working code:
public PeopleLine()
{
this.InitializeComponent();
(this.Content as FrameworkElement).DataContext = this;
}

Is there a way to set DataTemplate for a control in an user control (a composite one) in XAML

I have created a composite user control containing a Toolbar and a Datagrid, and expose them as public properties. Is there a way to add new button to Toolbar and set a DataTemplate for Datagrid in XAML, instead of implementing them in the code-behind file if I use this user control in another Window or user control?
I found a similar link here, but has no idea how to do it. Please help.
Here is the Xaml:
<UserControl x:Class="CRUDDataGrid1"
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" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ToolBarTray Grid.Row="0" >
<ToolBar x:Name="tb">
<Button x:Name="Add" Content="Add">
</Button>
</ToolBar>
</ToolBarTray>
<DataGrid Grid.Row="1" x:Name="dg">
</DataGrid>
</Grid>
</UserControl>
And here is the code-behind:
public partial class CRUDDataGrid1 : UserControl
{
public ToolBar ToolBar { get; set; }
public DataGrid DataGrid { get; set; }
public ObservableCollection<DataGridColumn> Columns { get; private set; } //edited
public CRUDDataGrid1()
{
InitializeComponent();
ToolBar = tb;
DataGrid = dg;
Columns = dg.Columns; //edited
}
}
And I want to use this user control in another user control like this:
<UserControl x:Class="UserControl1" ...>
<Grid>
<local:CRUDDataGrid1>
<local:CRUDDataGrid1.ToolBar>
<Button x:Name="Delete" Content="Delete">
</Button>
</local:CRUDDataGrid1.ToolBar>
<local:CRUDDataGrid1.DataGrid ItemsSource="{Binding Customers}">
<local:CRUDDataGrid1.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding XPath=#FirstName}" />
<DataGridTextColumn Header="Last Name" Binding="{Binding XPath=#LastName}" />
<local:CRUDDataGrid1.Columns>
</local:CRUDDataGrid1.DataGrid>
</local:CRUDDataGrid1>
</Grid>
</UserControl>
1 Foreword
Having a child control which owns a ToolBar and wanting a parent of that child control to add toolbar items to the ToolBar owned by the child is a tell-tale sign of bad
design. The primary and most important advice for you is to rethink your software design to avoid this kind of shared/split initialization.
In almost any scenario, you want the toolbar owned by the top-most control such as the main window, or a document window (in case your application has MDI or floating windows).
The toolbar items would be gathered from the respective controls housed within that window; for example, copy/paste/etc. actions from the document editor control, actions for creating or loading a new document from somewhere else, etc.
Side note: Often, such a design happens because novice WPF programmers want to realize button actions in the old-fashioned way of using Click event handlers. Such Click-event
handlers create code dependencies, and as long as they can be contained within just one (custom) control everything is fine. However, as soon as this is not feasible any more
(for example when an action should appear as a toolbar button or the same action should be triggered through a menu), trying to stick with Click event handlers will lead to convoluted code even for simple UIs and can cause severe headache...
The mechanism in WPF to avoid those pesky Click event handlers are Commands, or more specifically RoutedCommands. To be fair, it has to be noted that RoutedCommands have their own
share of challenges. However, many fine folks wrote many interesting and important things about using WPF's RoutedCommands and how to expand beyond their functionality, so the
only sane advice i can give here is to use the powers of Google if you want/need to know more.
2 Answering the question, but not solving the underlying design issue
To create a ToolBar which has collections of toolbar items defined at different places, while using multiple toolbar bands in the same ToolBarTray is not desired, the toolbar item collections need to be merged into a single list at some point. This can either be done somehow in code-behind, or it can be done in XAML with the help of a custom IMultiValueConverter.
The custom IMultiValueConverter - let's call it MergeCollectionsConverter - will be
agnostic to any data type. It just takes a number of IEnumerables and adds all their elements to the result list. It even accepts objects which are not IEnumerable, those objects themselves will be added to the result list.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows.Data;
using System.Windows.Documents;
namespace MyStuff
{
public class MergeCollectionsConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values == null) return null;
List<object> combinedList = new List<object>();
foreach (object o in values)
{
if (o is IEnumerable)
combinedList.AddRange( ((IEnumerable) o).Cast<object>() );
else
combinedList.Add(o);
}
return combinedList;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
I assume furthermore that the ToolBar inside of CRUDDataGrid1 should be fed from two toolbar item collections. The first collection with the default toolbar items is defined within CRUDDataGrid1. The second collection should allow other controls to append additional toolbar items after the default items; this collection therefore has to be publicly accessible.
Based on your example code from the question, your CRUDDataGrid1 class could look like the following (just considering the toolbar, it does not represent the complete class by any means):
CRUDDataGrid1.cs:
public partial class CRUDDataGrid1 : UserControl, INotifyPropertyChanged
{
public ObservableCollection<object> AdditionalToolbarItems { get { return _additionalToolbarItems; } }
private readonly ObservableCollection<object> _additionalToolbarItems = new ObservableCollection<object>();
public event PropertyChangedEventHandler PropertyChanged;
public UserControl1()
{
InitializeComponent();
_additionalToolbarItems.CollectionChanged +=
(sender, eventArgs) =>
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs("AdditionalToolbarItems"));
};
...other constructor code...
}
}
CRUDDataGrid1.xaml:
<DockPanel>
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<ToolBar.Resources>
<DataTemplate DataType="{x:Type My:UseCommand}">
<Button
Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"
Command="{Binding Command}"
CommandTarget="{Binding Target}"
CommandParameter="{Binding Parameter}"
Content="{Binding Command.Text}"
/>
</DataTemplate>
<My:MergeCollectionsConverter x:Key="convToolbarItems" />
<x:Array x:Key="defaultToolbarItems" Type="{x:Type sys:Object}">
<My:UseCommand Command="ApplicationCommands.New" />
<My:UseCommand Command="ApplicationCommands.Cut" />
<My:UseCommand Command="ApplicationCommands.Paste" />
</x:Array>
</ToolBar.Resources>
<ToolBar.ItemsSource>
<MultiBinding Converter="{StaticResource convToolbarItems}">
<Binding Source="{StaticResource defaultToolbarItems}" />
<Binding Path="AdditionalToolbarItems" ElementName="crudDataGrid1" />
</MultiBinding>
</ToolBar.ItemsSource>
</ToolBar>
</ToolBarTray>
<DataGrid x:Name="dg" />
</DockPanel>
The first collection is a 'static resource' in the ToolBar's resource directory,
identified by the resource key "defaultToolbarItems". The second is the collection provided by CRUDDataGrid1's property AdditionalToolbarItems. Using a <MultiBinding>
with the aforementioned converter, the merged list is bound to the ToolBar's ItemsSource.
Looking at the C# source code of the AdditionalToolbarItems property, you will notice the INotifyPropertyChanged implementation and the handler for the CollectionChanged
event. Why is that? Remember, that AdditionalToolbarItems is a read-only property. At the time the CRUDDataGrid1 control has been fully constructed, the data binding has been
set and AdditionalToolbarItems been processed by the multi binding. And it would never be
processed again, since the property itself will never change its value (it will
always refer to the same ObservableCollection). To make the <MultiBinding> re-evaluate the bound properties whenever the content of the AdditionalToolbarItems collection has
changed, the code needs to listen for CollectionChanged events and fire an
PropertyChanged event whenever the content of AdditionalToolbarItems has changed, which in turn will cause the <MultiBinding> to re-evaluate the bound properties.
You will also note the usage of <My:UseCommand> elements instead of using <Button>. Well, you could use <Button>, and it would work as well. Until your application wants to use multiple ToolBars at once sharing the same default buttons - in which case you have a problem: A button is a control and thus has one parent UI element. You cannot share
a button control amongst several toolbars, because a control can only be owned as a child by one parent UI element. Thus, RoutedCommands are used instead of button controls
(another, equally important reason will become obvious if you read the 'real' solution in section 3 below). Still, technically nothing would stop you from declaring <Button>
elements -- you could even mix <My:UseCommand> with <Button> (and other elements, as long as those can be rendered in the toolbar).
UseCommand is a pretty small and simple class which allows you to tell which command to use (plus optional CommandTarget and CommandParameter, if required):
namespace MyStuff
{
public class UseCommand
{
public System.Windows.Input.ICommand Command { get; set; }
public System.Windows.IInputElement Target { get; set; }
public object Parameter { get; set; }
}
}
The ToolBar will need a DataTemplate to properly display the command and its parameters stored inside UseCommand. You can see this DataTemplate as part of the ToolBar
resource dictionary in the XAML code above.
With these things in place, using CRUDDataGrid1 in UserControl1 and adding additional toolbar items could look like this:
<UserControl x:Class="UserControl1" ...>
<Grid>
<local:CRUDDataGrid1>
<local:CRUDDataGrid1.AdditionalToolbarItems x:Name="cdg">
<My:UseCommand Command="ApplicationCommands.Close" CommandTarget="{Binding ElementName=cdg}" />
<My:UseCommand Command="ApplicationCommands.New" CommandTarget="{Binding ElementName=cdg}" />
</local:CRUDDataGrid1.AdditionalToolbarItems>
...
</local:CRUDDataGrid1.DataGrid>
</Grid>
</UserControl>
For my example code, i used commands provided by System.Windows.Input.ApplicationCommands. You can ofcourse roll your own commands (as we will see below). Also note the demonstrated usage of the CommandTarget property. Whether using this property is necessary requires some understanding of how RoutedCommands work and mostly depends on where which element in the UI's visual/logical tree has established handlers for that particular command.
3 Using RoutedCommands to solve the design issue and the question
Having read section 2, you should already gotten the idea that RoutedCommands will help you to separate the provision of user-invokable actions by whatever component from the actual UI representation, and that this can help you avoiding the shenanigans about the somewhat convoluted composition of the ToolBar from different sources. Because, all that CRUDDataGrid1 essentially needs to provide for your GUI are the commands for a toolbar (or a menu, or any other command invokers for that matter).
From what i can glance from your source code, CRUDDataGrid1 is responsible for executing the "Add" action, whereas UserControl1 is responsible the "Delete" action.
Both actions should appear in the same toolbar.
Let's look at the "Add" action of CRUDDataGrid1. First and foremost, to make this action invokable through a RoutedCommand, an appropriate RoutedCommand object needs to be provided, obviously. You might choose one of the RoutedCommands provided by .NET (as declared in ApplicationCommands, ComponentCommand and NavigationCommand).
However, this is not always a good idea. Common commands such as ApplicationCommands.Copy can be executed by pretty much any control which supports clipboard operations, and knowing which actual control will handle the invocation of such a command requires knowing about how RoutedCommands are routed through the visual tree and how the logcial focus affects this routing. Thus, sometimes it is
easier to define your own RoutedCommand as a public static property - which we will do here for the "Add" action:
public partial class CRUDDataGrid1 : UserControl
{
public static readonly RoutedCommand AddCommand = new RoutedCommand("CRUDDataGridCommand.Add", typeof(CRUDDataGrid1));
public UserControl1()
{
InitializeComponent();
CommandBindings.Add(
new CommandBinding(
AddCommand,
OnExecutedAddCommand,
CanExecuteAddCommand
)
);
...other constructor code...
}
private void CanExecuteAddCommand(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = ...here your code that decides whether the "Add" command can execute
(and thus whether any button which uses this command will be enabled/disabled)
}
private void CanExecuteAddCommand(object sender, ExecutedRoutedEventArgs e)
{
...execute the "Add" action here...
}
}
Note the command-binding in the constructor as well as the respective methods handling the command. Just to avoid confusion: It is not required that the object serving as CommandTarget has to implement command-bindings. CommandTarget merely specifies the object in the visual/logical tree at which the routing starts.
While i do not show it here, the implementation regarding the DeleteCommand in UserControl is following the same pattern.
public partial class UserControl1 : UserControl
{
public static readonly RoutedCommand DeleteCommand = new RoutedCommand("UserControl1Command.Delete", typeof(UserControl1));
...same implementation approach as demonstrated for CRUDDataGrid1.AddCommand...
}
Creating the ToolBar can now happen entirely in UserControl1.xaml without worrying how the respective actions represented by the commands are executed. Note, that it is fine to use <Button> since the toolbar is entirely created in UserControl1 without a possibility that any of these buttons could be "shared" with another control. Also, note the absence of those helper classes like UseCommand and MergeCollectionsConverter which were required for the somewhat convoluted scenario in section 2 of my answer.
<UserControl x:Class="UserControl1" ...>
<DockPanel>
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<Button Content="Add" Command="{x:Static local:CRUDDataGrid1.AddCommand}" CommandTarget="{Binding ElementName=cdg}" />
<Button Content="Delete" Command="{x:Static local:UserControl1.DeleteCommand}" />
</ToolBar>
</ToolBarTray>
<local:CRUDDataGrid1 x:Name="cdg" ItemsSource="{Binding Customers}">
<local:CRUDDataGrid1.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding XPath=#FirstName}" />
<DataGridTextColumn Header="Last Name" Binding="{Binding XPath=#LastName}" />
</local:CRUDDataGrid1.Columns>
</local:CRUDDataGrid1>
</DockPanel>
</UserControl>
CRUDataGrid1 should probably directly inherit from the DataGrid type (not being a UserControl), implementing the extended CRUD functionality as you require.
By letting CRUDataGrid1 provide only RoutedCommands for any desired user-action, you and anybody else in your team is free in the decision where in the GUI to use the RoutedCommands - in tool bars, in menus or whereever else. You can use multiple buttons using the same command - no problem there. The infrastructure behind RoutedCommands also will take care about automatically enabling/disabling such buttons depending on the result of the CanExecute method bound to a command.
In the example given here, i did let CRUDataGrid1 and UserControl1 provide the RoutedCommands. But if you have many commands and more complex software, then there is nothing speaking against having a central place for defining those commands (similar to what Microsoft did with the RoutedCommands provided by the .NET framework).

Data binding to custom UserControl

So, I have made my own subclass of UserControl, called ChildView (I really can't come up with a decent name), that I want to show inside a container in a window, I have many different kinds of these UserControls and the window must be capable of showing all of them. The UserControls have implemented my subclass like this:
<src:ChildView x:Class="(namespace).LoginView" [...]>
public partial class LoginView : ChildView
And I have tried to add it to my window like so:
<Grid x:Name="ViewHolder" Grid.Column="1" Grid.Row="1">
<src:ChildView DataContext="{Binding CurrentView}" />
</Grid>
private ChildView _currentView;
public ChildView CurrentView
{
get { return _currentView; }
set
{
if (_currentView == value)
return;
_currentView = value;
smLog.Trace("View set to {0}", value.GetType().Name);
NotifyPropertyChanged("CurrentView");
}
}
However, this does not work. Nothing is shown in my container when I set CurrentView. There are no error messages in the output that would indicate a problem with the binding. Other data bindings in the window works. I can use my ChildViews by specifying their classes directly in the XAML, i.e:
<Grid x:Name="ViewHolder" Grid.Column="1" Grid.Row="1">
<src:LoginView />
</Grid>
I've read some about dependency properties but I don't think I need one here? I did try to implement one anyway but it didn't seem to help, though I probably made some mistake, I couldn't quite wrap my head around it...
So I guess my question is; do I need a dependency property? If so, how do I implement it in this case? If not, what is the problem?
Changing the Child's DataContext won't matter, you're trying to change the control itself, not the data it's bound to. What you need to do is add a placeholder control that would contain the actual view. WPF has such a thing built in, take a look at ContentControl.
Change your grid so it'll containt a ContentControl instead of ChildView, and bind the view to the control's Content property
<Grid>
<ContentControl Content="{Binding CurrentView}"/>
</Grid>

C# User Control that can contain other Controls (when using it)

I found something about this issue for ASP, but it didn't help me much ...
What I'd like to do is the following: I want to create a user control that has a collection as property and buttons to navigate through this collection. I want to be able to bind this user control to a collection and display different controls on it (containing data from that collection).
Like what you had in MS Access on the lower edge of a form ...
to be more precise:
When I actually use the control in my application (after I created it), I want to be able to add multiple controls to it (textboxes, labels etc) between the <myControly> and </mycontrol>
If I do that now, the controls on my user control disappear.
Here is an example of one way to do what you want:
First, the code - UserControl1.xaml.cs
public partial class UserControl1 : UserControl
{
public static readonly DependencyProperty MyContentProperty =
DependencyProperty.Register("MyContent", typeof(object), typeof(UserControl1));
public UserControl1()
{
InitializeComponent();
}
public object MyContent
{
get { return GetValue(MyContentProperty); }
set { SetValue(MyContentProperty, value); }
}
}
And the user control's XAML - UserControl1.xaml
<UserControl x:Class="InCtrl.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300" Name="MyCtrl">
<StackPanel>
<Button Content="Up"/>
<ContentPresenter Content="{Binding ElementName=MyCtrl, Path=MyContent}"/>
<Button Content="Down"/>
</StackPanel>
</UserControl>
And finally, the xaml to use our wonderful new control:
<Window x:Class="InCtrl.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:me="clr-namespace:InCtrl"
Title="Window1" Height="300" Width="300">
<Grid>
<me:UserControl1>
<me:UserControl1.MyContent>
<Button Content="Middle"/>
</me:UserControl1.MyContent>
</me:UserControl1>
</Grid>
</Window>
I'm having a hard time understanding your question, but I think what you're describing is an ItemsControl using DataTemplates to display the contents of (presumably) an ObservableCollection(T).
A UserControl may not be the best way to do this. You're wanting to add decorations around content, which is basically what Border does: it has a child element, and it adds its own stuff around the edges.
Look into the Decorator class, which Border descends from. If you make your own Border descendant, you should be easily able to do what you want. However, I believe this would require writing code, not XAML.
You might still want to make a UserControl to wrap the buttons at the bottom, just so you can use the visual designer for part of the process. But Decorator would be a good way to glue the pieces together and allow for user-definable content.
Here's a link to a built-in control (HeaderedContentControl) that does the same thing as the accepted answer except that it is an existing control in WPF since .Net 3.0

Categories

Resources