I have some problems with a WPF custom control, I'm trying to make it work but just don't get it:
Here is my problem, I'm creating a simple custom control that's almost the same to a TextBox. This control has a dependency property named "Texto", and the binding between the XAML and back-code of the custom control works fine, here is the code:
<UserControl x:Class="WpfCustomControlLibrary1.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"
mc:Ignorable="d"
d:DesignHeight="47" d:DesignWidth="147">
<Grid Height="43" Width="142">
<TextBox Height="23" HorizontalAlignment="Left" Margin="12,8,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" Text="{Binding Texto}"/>
</Grid>
And the dependency property code:
public static readonly DependencyProperty TextoProperty = DependencyProperty.Register("Texto", typeof(string), typeof(UserControl1));
public string Texto
{
get
{
return (string)GetValue(TextoProperty);
}
set
{
SetValue(TextoProperty, value);
}
}
Ok, now the problem: When I use this control in other windows I try to bind the "Texto" property to a viewmodel (as simple as everything else) but the property on the view model just dont change:
The ViewModel code:
public class ViewModelTest
{
public string SomeText { get; set; }
}
And the code of the applicatoin Window:
public ViewModelTest test;
public Window1()
{
InitializeComponent();
}
private void button1_Click_1(object sender, RoutedEventArgs e)
{
MessageBox.Show(test.SomeText);
MessageBox.Show(uc.Texto);
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
test = new ViewModelTest();
this.DataContext = test;
}
And the binding with the property of the view model:
<my:UserControl1 HorizontalAlignment="Left" Margin="27,12,0,0" Name="uc" VerticalAlignment="Top" Texto="{Binding Path=SomeText,Mode=TwoWay}"/>
Just for make it clearer, if I write "Hello" in the custom control and then I push the "button1", the first message shows nothing and the second message shows "Hello".
As you can see I'm fairly new into this, so I hope some of you can help me. Thanks.
Your binding Texto="{Binding SomeText}" works fine, the problem is the rebinding from your user control to the inner textbox. Remember binding will ALWAYS, if not modified, refere to the DataContext. But your DataContext doesn't contain the property Texto. Your control has that, To refere to that you need something called TemplateBinding, but this only works when you are in a ControlTemplate. Which you aren't so what is the solution?
You can use a special form of binding, by changing the source from the DataContext to a control with a given name: First give your UserControl a name
<UserControl x:Class="WpfCustomControlLibrary1.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"
mc:Ignorable="d"
x:Name="mainControl"
d:DesignHeight="47" d:DesignWidth="147">
and now change the binding to refere to the control, not the DataContext of the control anymore.
<TextBox Text="{Binding ElementName=mainControl, Path=Texto}"/>
Now your ViewModel binds to your user control and the content of the user control binds to the user controls Texto property.
Also one minor thing, what you called custom control, is in fact a user control, custom controls are something else.
Related
I want to pass a parameter "Trip" to the viewmodel of my navigated page. So far, i have this:
the code of my page from which i will navigate from. Its a Flip of trips
public sealed partial class TripOverview : Page
{
public TripOverview()
{
this.InitializeComponent();
DataContext = new TripOverviewViewmodel();
}
public void Trip_Detail(object sender, RoutedEventArgs e)
{
Trip selectedTrip = (Trip)TripFlip.SelectedItem;
this.Frame.Navigate(typeof(TripDetail), selectedTrip);
}
}
this is my Trip detail page. I want to add the parameter that i get from the onNavigatedTo method to the viewmodel which i then link to the Datacontext.
public sealed partial class TripDetail : Page
{
public Trip selectedTrip { get; set; }
public TripDetailViewmodel vm = new TripDetailViewmodel();
public TripDetail()
{
this.InitializeComponent();
this.DataContext = vm;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
Trip trip = (Trip)e.Parameter;
base.OnNavigatedTo(e);
}
}
So basicly i want the property "currentTrip in my viewmodel:
public class TripDetailViewmodel
{
public Trip CurrentTrip { get; set; }
public TripDetailViewmodel()
{
}
}
to be set without the use of mvvm light
Here is the XAML of my TripDetail page
<Page
x:Class="TravelChecker.TripDetail"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TravelChecker"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:viewmodel="using:TravelChecker.Viewmod"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.DataContext>
<viewmodel:TripDetailViewmodel x:Name="tripsDetailVm" />
</Page.DataContext>
<Grid>
<TextBlock x:Name="title" Text="{x:Bind tripsDetailVm.CurrentTrip.Destination.LocationName}"
FontFamily="Segoe UI" FontSize="26.667"
Foreground="Black" Padding="15,20" RenderTransformOrigin="0.318,0.392" />
<TextBox Text="{Binding ElementName=title, Path=Text, Mode=TwoWay}" x:Name="titletxt">
</TextBox>
</Grid>
When you use {x:Bind} to implement the data binding, the default source is the page instead of the DataContext. {x:Bind} will look in the code-behind of your page for properties, fields, and methods. To expose your view model to {x:Bind}, you will typically want to add new fields or properties to the code behind for your page.
But if you use {Binding} to implement data binding, {Binding} will need the DataContext.
Therefore, you could select the tripsDetailVm which is created in XAML’s Page.DataContext tag or the vm which is created in code behind as the binding source. However, whichever binding source you select, you need to set the CurrentTrip property as trip.
When you use tripsDetailVm as DataContext, you need to add the code tripsDetailVm.CurrentTrip = trip; to OnNavigatedTo method. Then {x:Bind} could find tripsDetailVm.CurrentTrip.Destination.LocationName in page and {Binding} could find title in DataContext(that is tripsDetailVm). Though the vm is initialized later than the page built, but it does not matter.
Please reference code below for context.
On start up, the Text of the 2 TextBoxes will be "This is the Original Value".
When the TestBox's button ("Test Button") is clicked:
the text of the TestBox's TextBox will change to "Set By Test Button"
the other TextBox's value will NOT change.
When the Window's button is clicked, the text of BOTH TextBoxes should change to "Set By Window". However, only the plain TextBox gets updated, the TestBox does not. <-- THIS IS THE BUG!
It seems that the way i'm (re)setting the Test property from within the TestBox obliterates the binding.
What is the proper way of changing a Dependency Property from within the user control itself without breaking bindings?
Example code:
I've got a UserControl, TestBox that looks like this:
TestBox.xaml:
<UserControl x:Class="Company.UserControls.TestBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="TextBoxControl">
<StackPanel>
<TextBox MinWidth="100" Name="TestTextBox"
Text="{Binding Path=Test, ElementName=TextBoxControl, Mode=TwoWay}"
/>
<Button MinWidth="100" Content="Test Button"
Click="ButtonBase_OnClick" />
</StackPanel>
</UserControl>
TestBox.xaml.cs:
using System.Windows;
namespace Company.UserControls
{
public partial class TestBox
{
public const string TestString = "Set By Test Button";
public TestBox()
{
InitializeComponent();
}
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register(
"Test",
typeof(string), typeof(TestBox),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender));
public string Test
{
get { return (string)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
/****** THIS OBLITERATES THE BINDING ******/
Test = TestString;
/****** THIS OBLITERATES THE BINDING ******/
}
}
}
And a Window that uses the control like this:
MainWindow.xaml:
<Window x:Class="Company.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:Company.UserControls"
Title="MainWindow">
<StackPanel x:Name="MyStackPanel">
<TextBox Text="{Binding Path=MyTestValue, Mode=OneWay}"/>
<u:TestBox x:Name="MyTestBox"
Test="{Binding Path=MyTestValue, Mode=OneWay}"/>
<Button Content="Click" Click="ButtonBase_OnClick" />
</StackPanel>
</Window>
MainWindow.xaml.cs:
using System.Windows;
namespace Company
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
MyStackPanel.DataContext = new MyThing
{
MyTestValue = "This is the Original Value"
};
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
MyStackPanel.DataContext = new MyThing
{
MyTestValue = "Set by Window"
};
}
}
public class MyThing
{
public string MyTestValue { get; set; }
}
}
The problem is that you are asking the binding system to get out of sync. The whole system is designed to keep all bound elements in sync. The only cases under which you can set a value on a dependency property without destroying the underlying binding are when the binding mode is set to "TwoWay" or "OneWayToSource". Under these conditions the value is transferred back to the source and consequently, the system is kept in sync. However, in your case a two way binding will cause both buttons to change both textboxes.
You will need to use two dependency properties TestBox. The first dependency property will be bound to the internal text box, and the second will be bound to in the parent window. Then you will need to add a property change handler to the second dependency property (which is done in the FrameworkPropertyMetadata). In this handler, simply set the value on the first dependency property.
Since you are using a UserControl with a code behind anyways, a simpler solution is to only have the second dependency property mentioned above and to directly set the value (from you event handler and the property change handler) onto the textbox via its x:Name.
Let me know if you need any more clarification.
I have to build a custom control. Simple TextBox with built-in validation which I can use in different parts of my app.
I did it this way:
I created new custom control (call it ValidTextBox) derived directly from TextBox;
It has its viewmodel (ValidTextBoxVM) with simple validation logic.
The Text property of the control is binded to Number property of the viewmodel.
ValidTextBoxVM code:
public class ValidTextBoxVM : INotifyPropertyChanged
{
#region INotifyPropertyChange implementation
private String _number;
public String Number
{
get { return _number; }
set
{
if (_number != value)
{
Validate(value);
_number = value;
RaisePropertyChanged("Number");
}
}
}
private void Validate(string number)
{
if (!string.IsNullOrEmpty(number) && number.Length > 10)
{
throw new ArgumentOutOfRangeException("Number too long.");
}
}
}
ValidTextBox.xaml code:
<TextBox x:Class="WpfApplication1.ValidTextBox"
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:vm="clr-namespace:WpfApplication1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Text="{Binding Number, ValidatesOnExceptions=True, UpdateSourceTrigger=LostFocus}">
<TextBox.DataContext>
<vm:ValidTextBoxVM/>
</TextBox.DataContext>
</TextBox>
I put my control on MainWindow and it worked perfectly. While losting focus - ViewModel raised the exception if validation process didn't pass - that's OK (code below).
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ctrl="clr-namespace:WpfApplication1"
xmlns:vm="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<ctrl:ValidTextBox Margin="5" Width="200" HorizontalAlignment="Left"/>
</StackPanel>
</Window>
The situation changed when I use seperate viewmodel for my MainWindow (MainWindowVM) and bind Text property of my control with field (MainNumber) in a MainWindowVM.
It's hidden my previous binding and validation has stopped to work (code below).
<StackPanel>
<ctrl:ValidTextBox Text="{Binding MainNumber, ValidatesOnExceptions=True, UpdateSourceTrigger=LostFocus}" Margin="5" Width="200" HorizontalAlignment="Left"/>
</StackPanel>
Is there any pattern that makes creating of self-validating controls possible. I found many solutions but with validation process outside the control.
The problem is setting DataContext on your TextBox. This means that when you write:
<ctrl:ValidTextBox Text="{Binding MainNumber
... the framework will attempt to resolve "MainNumber" on the ctrl:ValidTextBox object, which is the object's DataContext. (It's counter-intuitive, but that is how it works -- you should be able to see a binding error along the lines of "cannot find property "MainNumber" on the object "ValidTextBox", if you check the Visual Studio "Output" window.)
I have found that using control-specific view models is tricky in general, and leads to complications. I suggest avoiding that approach where possible. In this case, why not just extend TextBox and add a validation handler to the LostFocus event?
public class ValidTextBox : TextBox
{
public ValidTextBox()
{
LostFocus += ValidTextBox_LostFocus;
}
void ValidTextBox_LostFocus(object sender, System.Windows.RoutedEventArgs e)
{
// TODO validate
}
}
I'm using this
property control for WPF from Denis Vuyka.
I have the problem that it doesn't apply the new value of a property, if I don't press the TAB key.
So if I change a property in the property grid and then click the OK button, the property has still the previous value.
Sample code to reproduce:
public partial class MainWindow : Window
{
DataObject dataObject = new DataObject();
public MainWindow()
{
InitializeComponent();
propertyGrid.SelectedObject = dataObject;
}
private void OnOK(object sender, RoutedEventArgs e)
{
MessageBox.Show("Value of test is " + dataObject.test);
}
}
class DataObject
{
public int test { get; set; }
public int test2 { get; set; }
}
<Window x:Class="PropGridTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pg="http://schemas.denisvuyka.wordpress.com/wpfpropertygrid"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Grid.Row="0" IsDefault="True" Click="OnOK">OK</Button>
<pg:PropertyGrid x:Name="propertyGrid" Grid.Row="1">
</pg:PropertyGrid>
</Grid>
</Window>
Just type a number into property test and then click the OK button.
Does anybody know a workaround for this problem?
This is what I tried in OnOK so far to no avail:
propertyGrid.Focus();
propertyGrid.MoveFocus(new System.Windows.Input.TraversalRequest(System.Windows.Input.FocusNavigationDirection.Next));
System.Windows.Forms.SendKeys.SendWait("{TAB}");
You'd need to edit the source code and change the binding on the text editor so that it uses UpdateSourceTrigger=PropertyChanged.
To find the area of source that needs updating you can use Snoop to inspect the control.
Get your application running, fire up snoop, pick your application from the drop down menu on the Snoop tool, and click the binoculars. Now if you hold shift and ctrl keys while you hover the cursor over the control you'll be able to see its type and all of its properties.
After that you just need to search the solution to find that type and edit the binding in the XAML. Take a look at this page for info on how to use the UpdateSourceTrigger binding property.
I do not know exactly for this grid (I use this one), but I have the same problem there. It seems to be a common problem. Try to remove focus from PropertyGrid to another control before selecting new object ot clearing selected object property. For example:
public static void UpdatePropertyGridObjects(object objToSelect)
{
Components.DockManager.Focus();
Components.PropertyGrid.SelectedObject = objToSelect;
}
I have trouble to understand how dependency properties can be used between C# and xaml code.
This is a smal code example of my question
XAML code:
<Window x:Class="WpfChangeTextApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Label Name="statusTextLabel" Content="{Binding StatusText}"></Label>
<Button Name="changeStatusTextButton" Click="changeStatusTextButton_Click">Change Text</Button>
</StackPanel>
C# code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public string StatusText
{
get { return (string)GetValue(StatusTextProperty); }
set { SetValue(StatusTextProperty, value); }
}
// Using a DependencyProperty as the backing store for StatusText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty StatusTextProperty =
DependencyProperty.Register("StatusText", typeof(string), typeof(MainWindow));
private void changeStatusTextButton_Click(object sender, RoutedEventArgs e)
{
StatusText = "Button clicked";
}
}
So, my trouble is that Label statusTextLabel dose not get updated when I click on the button. My trouble is that I don't know in what part of the code that I'm doing something wrong, is it in the xaml or in the C#? In the xaml I might doing something wrong in the Binding? Or have I missed doing something in the C# code?
By default, binding paths are relative to the DataContext property of the current element. You have not set it to anything, so it can't resolve the binding. If you want the StatusText property on your window class, then there are two approaches. One is to use a binding with a RelativeSource of FindAncestor to find the Window in the tree and bind to its properties directly:
<Label Name="statusTextLabel" Content="{Binding StatusText,
RelativeSource={RelativeSource AncestorType=Window}}"></Label>
The other is to set the DataContext of the Window to itself, so it will be inherited by the label. For example, in your constructor:
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
}
For most applications, you will actually want a separate class to represent the data, and you will set an instance of that class as the DataContext. You can also use ordinary CLR properties instead of dependency properties, although you will need to implement INotifyPropertyChanged if you want to UI to be informed when properties change. Dependency properties are more useful when you are writing a custom control and you want users to be able to set the properties using data binding.