UserControl using WPF, MVVM and DP [duplicate] - c#

This question already has answers here:
XAML binding not working on dependency property?
(1 answer)
Issue with DependencyProperty binding
(3 answers)
Closed 4 years ago.
I am designing a NumericUpDownControl UserControl and have successfully implemented it in my MainWindow.xaml as follows:
<UserControl x:Class="SettingsDialog.Controls.NumericUpDownControl"
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:local="clr-namespace:SettingsDialog.Controls"
xmlns:viewModels="clr-namespace:SettingsDialog.ViewModels"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="18"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox Text="{Binding Value}" Grid.Row="0" Grid.RowSpan="2" Height="20" VerticalContentAlignment="Center"/>
<RepeatButton Content="5" Grid.Column="1" Grid.Row="0" FontSize="8" Height="10" FontFamily="Marlett" VerticalContentAlignment="Center"/>
<RepeatButton Content="6" Grid.Column="1" Grid.Row="1" FontSize="8" Height="10" FontFamily="Marlett" VerticalContentAlignment="Center"/>
</Grid>
</UserControl>
In the code-behind of my UserControl, I have defined the following:
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value", typeof(int),
typeof(NumericUpDownControl)
);
public int Value
{
get => (int)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
It works fine as I can use it in my MainWindow.xaml like this:
<controls:NumericUpDownControl Width="100" Value="10"/>
However, when I attempt to set a ViewModel for my UserControl, the TextBox within the UserControl no longer recognizes the Value dependency property. How can I properly implement a ViewModel for my UserControl while still allowing for the ability to set the Value property from outside the control? Is there an issue with my current implementation that is causing this issue?

This should bind the Text property of the TextBox to the Value property of the UserControl regardless of its DataContext:
<TextBox Text="{Binding Value, RelativeSource={RelativeSource AncestorType=UserControl}}" Grid.Row="0" Grid.RowSpan="2" Height="20" VerticalContentAlignment="Center"/>

You cannot bind a UserControl to itself and to a ViewModel at the same time.
You would have to remove DataContext="{Binding RelativeSource={RelativeSource Self}}" from within the UserControl and make sure to pass the right DataContext to the UserControl when integrating it in your UI.

Since you are making a sort of custom control another way to solve your problem is simply to use the propertychanged callback of your dependency property and every time it changes you simply update your Textbox's Text property:
public partial class NumericUpDownControl : UserControl
{
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(int),typeof(NumericUpDownControl),new PropertyMetadata(OnValueChanged));
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((NumericUpDownControl) d).TextBox.Text = e.NewValue?.ToString() ?? string.Empty;
}
public int Value
{
get => (int)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
public NumericUpDownControl()
{
InitializeComponent();
}
}
}

Related

ViewModelLocator in PRISM (Unity) does not work in modules

I am following the Brian Lagunas's tutorial and have a question.
We have a small application setup with PRISM 6 and Unity bootstrapper. I want to use the ViewModelLocator to bind a ViewModel to my view. This works in the "base module" (the one with Shell and bootstrapper), but it seems that there are some issues when using it in other modules.
Below you can find the classes for View (XAML and code behind) and for ViewModel. The binding works if I set the DataContext in the code behind manually. while debugging the code I found out that the ViewModel class is never instantiated so I guess the VML cannot find the VM because of wrong configuration. But as far as I can see the naming conventions are fulfilled.
Can you find the issue or have an idea what configuration I did forget?
View(code behind):
namespace CommunicationModule.Views
{
public partial class CommunicationView : UserControl, IView
{
public CommunicationView()
{
InitializeComponent();
}
}
}
View XAML:
<UserControl
x:Class="CommunicationModule.Views.CommunicationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:View="clr-namespace:CommunicationModule.Views"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid DataContext="{Binding ModelList, UpdateSourceTrigger=PropertyChanged}" Width="320" Height="300">
<Grid.Resources>
<DataTemplate x:Key="DataTemplate">
<Border Name="border" BorderBrush="DarkSlateBlue" BorderThickness="2"
CornerRadius="2" Padding="5" Margin="5">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition MaxWidth="30"/>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Image Grid.Row="0" Grid.Column="0" Source="{Binding Path=IconUri}" HorizontalAlignment="Left" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=Title}" FontSize="12" FontWeight="Bold" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Path=ContentShort}"/>
</Grid>
</Border>
</DataTemplate>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<!-- UI -->
<DockPanel Grid.ColumnSpan="2" Margin="0,0,10,0">
<!-- Title -->
<Label DockPanel.Dock="Top" FontSize="18" Margin="5" Content="Wichtige Meldungen"/>
<!-- Data template is specified by the ItemTemplate attribute -->
<ScrollViewer>
<ListBox Name="listBox"
SelectionMode="Single"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource DataTemplate}"
HorizontalContentAlignment="Stretch"
IsSynchronizedWithCurrentItem="True"
Margin="5,0,5,5" Width="280"/>
</ScrollViewer>
</DockPanel>
</Grid>
</UserControl>
ViewModel:
namespace CommunicationModule.ViewModels
{
public class CommunicationViewViewModel : BindableBase
{
private List<CommunicationModel> _modelList = (new CommunicationModelBO()).getCommunicationItems(); //= new List<CommunicationModel>();
private readonly IRegionManager _regionManager;
public List<CommunicationModel> ModelList
{
get { return _modelList; }
set { SetProperty<List<CommunicationModel>>(ref _modelList, value); }
}
public CommunicationViewViewModel(IRegionManager regionManager)
{
_regionManager = regionManager;
}
}
}
You can not let your usercontrol's name end with View,if you do so, the AutoWireViewModel won't work. So please change your usercontrols name to Communication and viewmodels name to CommunicationViewModel.

DataItem=null on binding, can't find out why?

I am trying to reproduce what is suggested in Sheridan's answer to this question to navigate trough my views when using WPF with the MVVM pattern. Unfortunately, I am getting a binding error when I do so. Here is the exact error:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='JollyFinance.ViewModels.MainViewModel', AncestorLevel='1''. BindingExpression:Path=DataContext.DisplayTest; DataItem=null; target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')
When I look into my xaml code in LoginView.xaml, I noticed that Visual Studio is telling me that it cannot find DataContext.DisplayText in context of type MainViewModel. I have tried removing DataContext. and just keeping DisplayText instead, but to no avail.
Unless Sheridan's answer has an error, I am most definitely missing something here. What should I do for it to work?
MainWindow.xaml:
<Window x:Class="JollyFinance.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:JollyFinance.ViewModels"
xmlns:views="clr-namespace:JollyFinance.Views"
Title="JollyFinance!" Height="720" Width="1280">
<Window.Resources>
<!-- Different pages -->
<DataTemplate DataType="{x:Type vm:LoginViewModel}">
<views:LoginView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:TestViewModel}">
<views:Test/>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<Grid>
<ContentControl Content="{Binding CurrentViewModel}"/>
</Grid>
</Window>
MainViewModel.cs:
public class MainViewModel : BindableObject
{
private ViewModelNavigationBase _currentViewModel;
public MainViewModel()
{
CurrentViewModel = new LoginViewModel();
}
public ICommand DisplayTest
{
get
{
// This is added just to see if the ICommand is actually called when I press the
// Create New User button
Window popup = new Window();
popup.ShowDialog();
// View model that doesn't contain anything for now
return new RelayCommand(action => CurrentViewModel = new TestViewModel());
}
}
public ViewModelNavigationBase CurrentViewModel
{
get { return _currentViewModel; }
set
{
if (_currentViewModel != value)
{
_currentViewModel = value;
RaisePropertyChanged("CurrentViewModel");
}
}
}
}
LoginView.xaml:
<UserControl x:Class="JollyFinance.Views.LoginView"
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:vm="clr-namespace:JollyFinance.ViewModels"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<vm:LoginViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Username: " Grid.Column="1" Grid.Row="1" Margin="5"/>
<TextBox Text="{Binding Path=Username}" Grid.Column="2" Grid.Row="1" Grid.ColumnSpan="2" Margin="5"/>
<TextBlock Text="Password: " Grid.Column="1" Grid.Row="2" Margin="5"/>
<PasswordBox x:Name="PasswordBox" PasswordChar="*" Grid.Column="2" Grid.ColumnSpan="2" Grid.Row="2" Margin="5"/>
<Button Content="Log In" Grid.Column="2" Grid.Row="3" Margin="5" Padding="5" Command="{Binding LoginCommand}"/>
<Button Content="Create new user" Grid.Column="3" Grid.Row="3" Margin="5" Padding="5"
Command="{Binding DataContext.DisplayTest, RelativeSource={RelativeSource AncestorType={x:Type vm:MainViewModel}},
Mode=OneWay}"/>
</Grid>
</UserControl>
LoginViewModel.cs:
public class LoginViewModel : ViewModelNavigationBase
{
public LoginViewModel()
{
LoginCommand = new RelayCommand(Login);
}
private void Login(object param)
{
// Just there to make sure the ICommand is actually called when I press the
// Login button
Window popup = new Window();
popup.ShowDialog();
}
public String Username { get; set; }
public String Password { get; set; }
public ICommand LoginCommand { get; set; }
}
ViewModelNavigationBase is just a class that implements the INotifyPropertyChanged interface, and Test.xaml and TestViewModel.cs are just a dummy viewmodel/view for test purposes.
In my answer, I stated that you should declare your view model DataTemplates in App.xaml so that every view will have access to them. Putting them in the MainWindow class is your first problem.
Another mistake is your Binding Path for your ICommand. If you want to access something from the view model that is set as the Window.DataContext, then you should not use a RelativeSource Binding . Try this instead:
<Button Content="Create new user" Grid.Column="3" Grid.Row="3" Margin="5" Padding="5"
Command="{Binding DataContext.DisplayTest}, Mode=OneWay}" />
Also remember that for whatever reason, you chose not to make your MainViewModel class extend the ViewModelNavigationBase class... that could also cause you problems.
Anyway, if that doesn't sort out your problems, just let me know. Also, if you want to notify a user at anytime on Stack Overflow, just put an # symbol in front of their name and they will receive a notification. You could have asked me this question directly if you had done that.
MainViewModel is not a direct ancestor in the visual or logical tree, which is why RelativeSource={RelativeSource AncestorType={x:Type vm:MainViewModel}} cannot find it.
How do you fix it? First, please don't try and reach through various UI components like this to trigger commands. Just because you saw it somewhere on the internet doesn't mean it is a desirable design choice. Doing this means the LoginView has a deep understanding of other views and view models - which is bad. If you are going to do that then you might as well code everything as one single UI class with a single viewmodel that is really just a massive code behind class.
A better (but still not optimal) approach is to have the MainView (or viewmodel) spawn the LoginView. As it holds the reference to the view, it is also responsible for disposing of it. So the LoginView can be shown to collect credentials, then the main view can dispose if it signals that the credentials are validated successfully. Or it can just collect credentials and leave it up to the MainView/viewmodel to validate them (which can be done by the MainView/viewmodel triggering a background call to check the credentials against a store).
A simple (crude) rule of thumb is: a parent view can know about a child view, but in general the reverse should not happen. MVVM is about decoupling and segregating functionality, but instead you are tightly coupling them. Of course all this gets a whole lot more complex than what I've illustrated, but you can still do some of this while keeping it practical and not over-engineering.
So, TLDR;:
the LoginView (or its viewmodel) should implement its own command to deal with the button click
don't reach deep through the entrails of another view to trigger functionality
strive for SRP and de-coupled code/views
when using ancestor binding, look for something that's in the visual/logical tree
Define MainViewModel in App scope as a static resource.
<App.Resources>
<MainViewModel x:Key="MainViewModel" />
</App.Resources>
Then you will be able to bind MainViewModel commands from any view.
<Button Command="{Binding Source={StaticResource MainViewModel}, Path=DisplayTest}" />
EDIT
Or try this code:
<Button Command="{Binding DisplayTest, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window), Path=DataContext}}"/>

Proper Code-Behind Object Data Binding in XAML?

I am currently binding an object in my code-behind (C#) to my XAML by giving a name to the XAML control, and setting the DataContext in the code-behind.
public partial class SmsControl: UserControl
{
private readonly DataOrganizer _dataOrganizer;
public FunctionalTester _funcTester;
public SmsControl()
{
InitializeComponent();
_dataOrganizer = new DataOrganizer();
_funcTester = new FunctionalTester();
// Set the datacontext appropriately
grpModemInitialization.DataContext = _funcTester;
}
private async void Button_Click_2(object sender, RoutedEventArgs e)
{
await _funcTester.Test();
}
}
And my XAML...
<!-- SMS Test Modem Initialization GroupBox -->
<GroupBox x:Name="grpModemInitialization" Grid.Column="1" Grid.Row="1" Grid.ColumnSpan="3" Style="{StaticResource groupboxViewItem}">
<GroupBox.Header>
<Label Content="SMS Test Modem Initialization" Style="{StaticResource boldHeaderItem}"/>
</GroupBox.Header>
<!-- SMS Test Modem Initialization Grid -->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" Content="COM:" Style="{StaticResource boldHeaderItem}" />
<ComboBox Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" Style="{StaticResource comboBoxItem}" ItemsSource="{Binding AvailableCommPorts}" SelectedItem="{Binding SelectedCommPort}" />
<Label Grid.Column="2" Grid.Row="0" Content="Modem Ready:" Style="{StaticResource boldHeaderItem}" />
<Label Grid.Column="2" Grid.Row="1" Content="RSSI:" Style="{StaticResource boldHeaderItem}" />
<Label Content="{Binding ModemReady}" Grid.Column="3" Grid.Row="0" HorizontalContentAlignment="Left" VerticalAlignment="Center"/>
<Label Content="{Binding ModemRssi}" Grid.Column="3" Grid.Row="1" HorizontalContentAlignment="Left" VerticalAlignment="Center" />
<Label Grid.Column="4" Grid.Row="0" Content="Modem #:" Style="{StaticResource boldHeaderItem}"/>
<Button Grid.Column="4" Grid.Row="1" Grid.ColumnSpan="2" Content="Initialize" />
<Label Content="{Binding ModemNumber}" Grid.Column="5" Grid.Row="0" HorizontalContentAlignment="Left" VerticalAlignment="Center"/>
</Grid>
</GroupBox>
The code above works fine - no problems. What I'm asking is, if there is a way to set the DataContext of the GroupBox in XAML, referencing my _funcTester object, instead of setting the DataContext in the code-behind? The reason I ask, is because different controls need to be bound to different objects in the code-behind and I'm not finding good resources on how to do so, except as I show above (giving a "x:Name" to each XAML control and setting the DataContext in code-behind). Any help is appreciated! Thanks!
You don't want to reference UI elements by name in the code-behind. Actually any time you can avoid naming an object you save a little in performance. And by setting up your app to use MVVM properly, you gain in performance, readability, maintainability, and code separation.
You want to abstract things further to use the MVVM pattern. You're doing your bindings correctly but consider the pattern. Your view is all correct. Consider adding a class that holds the properties defined currently in your code-behind and the methods called in your event handlers.
public class ViewModel : INotifyPropertyChanged
{
private FunctionalTester _funcTester;
public FunctionalTester FuncTester
{
get
{
return _funcTester;
}
set
{
_funcTester = value;
OnPropertyChanged( "FuncTester" );
}
}
public async void TestAsync( )
{
await _funcTester.Test( );
}
}
A binding to the FuncTester would simply be SomeProperty="{Binding FuncTester}" because the object is set as the DataContext of your view. A decent article that expands on this idea is found here.
Obviously left out some things (like INotifyPropertyChanged implementation and other properties you've defined) for brevity. But just make this class and assign it as your view model. Then the UI (the Xaml and the code-behind) only really deal with the UI and the ViewModel really deals with the data and logic. Great separation. For your event handler, just call ((ViewModel)this.DataContext).Test( ); and you can plug-and-play your ViewModel's to change functionality on the fly. Hope this helps!
Just set the DataContext of whole UserControl to self i.e do
this.DataContext = this; in constructor.
Then define the Property for _functinTester
public FunctionalTester FuncTester { get {return _funcTester} };
Now in your xaml you can do
<GroupBox x:Name="grpModemInitialization" DataContext="{Binding FuncTester}"/>
In this way since you have the DataContext set for your whole usercontrol, you can bind any control to any property within that DataContext

Binding UserControl Dependency Properties to Runtime DataSet

I'm quite new to xaml. I have looked around quite a bit and I'm finding posts that come close to what I'm trying to do, but I haven't been able to find exactly what I'm looking for.
I have an external WPF UserControl with Dependency Properties. In another project, I've imported the dll from my UserControl library. I created a static grid in my project in which there is an instance of my usercontrol in each cell. Each UserControl DependencyProperty I've defined is available in xaml.
At runtime, I will fill a DataTable with data then I need to bind the UserControls in my grid to the DataTable, so that each UserControl reflects values from the DataTable. Each row in the DataTable will have several column values that I need to bind to my UserControl's Dependency Properties.
What I'm having trouble with is defining the DataContext after the data is retrieved in such a way that I can define a binding path for the Dependency properties in each one of my UserControls. If I set the DataContext of my grid to the DataTable like this:
Grid.DataContext = myDataTable;
Then what is the syntax for the binding path of the UserControl property?
<my:ucControl Name="myUserControl1" MyDependencyProp="{Binding Path=??}" Grid.Column="1" Grid.Row="1" />
I've seen posts where some have used "Row[0][column_name]" or variations of that, but I can't seem to get anything like that working. Am I approaching the binding method wrong?
Here is my UserControl code:
public partial class ucControl : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public static DependencyProperty IndexProperty =
DependencyProperty.Register("Index", typeof(int), typeof(ucControl));
public static DependencyProperty StatusProperty =
DependencyProperty.Register("Status", typeof(String), typeof(ucControl),
new PropertyMetadata(string.Empty, new PropertyChangedCallback(NotifyPropertyChanged)));
#region PublicProperties
public int Index
{
get { return (int)GetValue(IndexProperty); }
set { SetValue(IndexProperty, value); }
}
public String Status
{
get { return (int)GetValue(StatusProperty); }
set { SetValue(StatusProperty, value); }
}
#endregion
}
Here is the XAML:
<Grid Canvas.Left="17" Canvas.Top="46" Name="MasterGrid" Width="1259" HorizontalAlignment="Left" VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Name="ColumnHeader" Height="25" />
<RowDefinition Height="40" />
<RowDefinition Height="40" />
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Name="DateColumn" Width="80"/>
<ColumnDefinition Width="65"/>
<ColumnDefinition Width="65"/>
<ColumnDefinition Width="65"/>
</Grid.ColumnDefinitions>
<my:ucControl Name="myControl1" Index="{Binding ??}" Status="{Binding ??}" Grid.Column="1" Grid.Row="1" Grid.RowSpan="1" />
<my:ucControl Name="myControl2" Index="{Binding ??}" Status="{Binding ??}" Grid.Column="1" Grid.Row="2" Grid.RowSpan="1" />
<my:ucControl Name="myControl3" Index="{Binding ??}" Status="{Binding ??}" Grid.Column="1" Grid.Row="2" Grid.RowSpan="1" />
</Grid>
Here is my C#:
String strViewSelect = "SELECT index, status, FROM Table1 ORDER BY index ASC;
System.Data.DataSet dsMonthData = dataClass.mFillDataset(strViewSelect);
MasterGrid.DataContext = dsMonthData;
UPDATED: Here's how I've solved this issue:
First, I'm creating a static DataSource in my project that contains the columns that I want to bind to. Adding arguments to the query so I can specify the data that I want to retrieve.
The following was added to the XAML in my window:
<Window.Resources>
<my1:myDataSet x:Key="myDataSet" />
<CollectionViewSource x:Key="tableNameViewSource" Source="{Binding Path=tableName, Source={StaticResource myDataSet}}" />
</Window.Resources>
Next, I've added the following XAML to my grid:
DataContext="{StaticResource tableNameViewSource}"
Now, I can add the bindings to my UserControl properties:
Status="{Binding Path=statusColumn}"
<my:ucCalendarCell Name="ucControl1" Status="{Binding Path=statusColumnName}" Index="{Binding Path=indexColumnName}" Grid.Column="1" Grid.Row="1" Grid.RowSpan="1" />
A little bit of work left to tailor to my needs, but that's the basic idea. I'm positive that is more than one way to do this, but this should work for me.

How can i customize usercontrol from parent window

I want to know how can i change user control from my Parent window that i placed usercontrol into it.
I have an user control that have a grid and data grid in that. now i want change data grid properties in my window ... and i want add another control to my grid .
some thing like this
<window>
<usercontrol>
<usercontrol.datagrid backcolor=#ff00000>
<usercontrol/>
<window/>
or can i add a textblock into usercontrol grid like this code :
<window>
<usercontrol.grid>
<textblock grid.row=1/>
<usercontrol.grid/>
<window/>
All element in user control are public so i can make change from c# code but i want do that with xaml design mode
in windows form i create a user control inherit from data grid view then i custom it. i use it in 10 windows and in 11th window i need change data grid view a bit i dont change usercontrol because it change all windows so i just change that usercontrol is in 11th window
please help !
I think you should create a DependencyProperty for your DataGrid's BackgroundColor (or whatever property you want to change) inside your UserControl's code behind:
public static DependencyProperty GridColorProperty = DependencyProperty.Register("GridColor", typeof (Brush),
typeof (UserControl1),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions
.AffectsRender));
public Brush GridColor
{
get { return (Brush)GetValue(GridColorProperty); }
set { SetValue(GridColorProperty, value);}
}
After that you should bind your DataGrid's Color property to it in UserControl's XAML:
<DataGrid Background="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=YourControlType}, Path=GridColor}"/>
And now you can use the control like that:
<YourControlType GridColor="Green"/>
As for controls addition it depends on what outlook exactly you're trying to achieve. The most straightforward way would be to derive your user control from grid. Or may be deriving from ContentControl would be enough for your purposes
Edit:
That's how you could put inside a new control. Deriving your control from Grid:
<Grid x:Class="WpfApplication3.UserControl1"
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:app="clr-namespace:WpfApplication3" mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<DataGrid Background="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=app:YourControlType}, Path=GridColor}"/>
</Grid>
And you would use it like that:
<YourControlType GridColor="Green">
<Button Grid.Row="1"/>
</YourControlType>
But actually it's a pretty weird thing to do and I would better derive it from a ContentControl:
<ContentControl x:Class="WpfApplication3.YourControlType"
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:app="clr-namespace:WpfApplication3" mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<ContentControl.Template>
<ControlTemplate TargetType="ContentControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<DataGrid Background="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=app:YourControlType}, Path=GridColor}"/>
<ContentPresenter Content="{TemplateBinding Content}" Grid.Row="1"/>
</Grid>
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
That's how you use it:
<YourControlType GridColor="Green">
<Button/>
</YourControlType>
As yet another possibility you could create a dependency property for your control's content. Code behind:
public static readonly DependencyProperty InnerContentProperty =
DependencyProperty.Register("InnerContent", typeof (FrameworkElement), typeof (YourControlType),
new FrameworkPropertyMetadata(default(FrameworkElement),
FrameworkPropertyMetadataOptions.AffectsRender));
public FrameworkElement InnerContent
{
get { return (FrameworkElement) GetValue(InnerContentProperty); }
set { SetValue(InnerContentProperty, value); }
}
UserControl's XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<DataGrid Background="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=WpfApplication3:UserControl1}, Path=GridColor}"/>
<ContentControl Grid.Row="1" Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=app:YourControlType}, Path=InnerContent}"/>
</Grid>
Usage:
<YourControlType GridColor="Green">
<YourControlType.InnerContent>
<Button/>
</YourControlType.InnerContent>
</YourControlType>
But if you want just a quick and simple answer to your initial question as it states, there is no way you can directly address an inner control of your UserControl from XAML. = )

Categories