I have a User Control Library that I am loading dynamically. From that lib I am inserting a Tabitem into a TabControl. I can load the tab and show it without error. However, I can't seem to get the binding on the control working.
This is the code I use to load it and add it to the TabControl:
Assembly moduleAssembly = Assembly.Load("ControlLib");
UserControl uc = (UserControl)Application.LoadComponent(new System.Uri("/ControlLib;component/UserControl1.xaml", UriKind.RelativeOrAbsolute));
TabControl itemsTab = (TabControl)this.FindName("mainTabControl");
TabItem newTab = new TabItem();
newTab.Content = uc;
newTab.Header = "Test";
itemsTab.Items.Add(newTab);
itemsTab.SelectedItem = newTab;
This is the C# code for the control:
public partial class UserControl1 : UserControl
{
public static readonly DependencyProperty TestStringProperty =
DependencyProperty.Register("TestString", typeof(string), typeof(UserControl1));
public string TestString { get; set; }
public UserControl1()
{
InitializeComponent();
TestString = "Hello World";
}
}
This is the XAML code for the control:
<UserControl x:Class="ControlLib.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="300" d:DesignWidth="300">
<Grid>
<TextBox Height="30" Width="100" HorizontalAlignment="Left" VerticalAlignment="Bottom" Text="{Binding Path=TestString, Mode=TwoWay}" />
</Grid>
</UserControl>
When the tab displays all I see if a blank in the TextBox rather than "Hello World"
What am I missing?
You would still be setting the DataContext of your user control to instance of the class. Just how you go about creating that instance differs as you would be loading that dll a runtime. But fundamentally the binding setup remains the same.
var assembly = Assembly.LoadFrom(#"yourdllname.dll");
Type type = assembly.GetType("ClassLibrary1.SampleViewModel");
object instanceOfMyType = Activator.CreateInstance(type);
DataContext = instanceOfMyType;
For how basic databinding works read MSDN documentation.
Make sure you select the correct framework on the top of the screen.
EDIT
Usually this is created as a separate class (ViewModel in MVVM pattern).
public partial class Window3 : Window, INotifyPropertyChanged
{
public Window3()
{
InitializeComponent();
DataContext = this;
TestString = "Hello World.";
}
string _testString;
///<summary>Gets or sets TestString.</summary>
public string TestString
{
get { return _testString; }
set { _testString = value; OnPropertyChanged("TestString"); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
var e = new PropertyChangedEventArgs(propertyName);
PropertyChanged(this, e);
}
}
}
Related
I'm looking to synchronize between a text in the textbox and string in a variable. I found how to get the index in which the string was changed (in the textbox), the length added and length removed, but how can I actually find the string added?
So far I've used TextChangedEventArgs.Changes, and got the properties of the items in it (ICollection).
I'm trying to create a password box in which I could show the actual password by a function. hence I do not want the textbox to synchronize directly (for example, in the textbox would appear "*****" and in the string "hello").
If you want only text added you can do this
string AddedText;
private void textbox_TextChanged(object sender, TextChangedEventArgs e)
{
var changes = e.Changes.Last();
if (changes.AddedLength > 0)
{
AddedText = textbox.Text.Substring(changes.Offset,changes.AddedLength);
}
}
Edit
If you want all added and remove text you can do this
string oldText;
private void textbox_GotFocus(object sender, RoutedEventArgs e)
{
oldText = textbox.Text;
}
string AddedText;
string RemovedText;
private void textbox_TextChanged(object sender, TextChangedEventArgs e)
{
var changes = e.Changes.Last();
if (changes.AddedLength > 0)
{
AddedText = textbox.Text.Substring(changes.Offset, changes.AddedLength);
if (changes.RemovedLength == 0)
{
oldText = textbox.Text;
RemovedText = "";
}
}
if (changes.RemovedLength > 0)
{
RemovedText = oldText.Substring(changes.Offset, changes.RemovedLength);
oldText = textbox.Text;
if (changes.AddedLength == 0)
{
AddedText = "";
}
}
}
DataBinding is the most common way in WPF to show and collect data in a UI
Try this:
<Window x:Class="WpfApp3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp3"
mc:Ignorable="d"
Title="MainWindow"
Height="350"
Width="525">
<Grid>
<TextBox Text="{Binding Path=SomeText, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Left"
Margin="101,83,0,0"
VerticalAlignment="Top"
Width="75" />
<TextBlock Text="{Binding SomeText}"
HorizontalAlignment="Left"
Margin="101,140,0,0"
VerticalAlignment="Top"
Width="75" />
</Grid>
</Window>
Code for the window:
public partial class MainWindow : Window
{
private readonly AViewModel viewModel = new AViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = viewModel;
}
}
And the code for the ViewModel that holds the data you want to show and collect:
public class AViewModel : INotifyPropertyChanged
{
private string someText;
public string SomeText
{
get
{
return someText;
}
set
{
if (Equals(this.someText, value))
{
return;
}
this.someText = value;
this.OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(
[CallerMemberName]string propertyName = null)
{
this.PropertyChanged?.Invoke(
this,
new PropertyChangedEventArgs(propertyName));
}
}
Although this looks complicated for a simple scenario it has a lot of advantages:
You can write automated (unit)test for the ViewModel without creating a UI
Adding extra fields and logic is trivial
If the UI needs to change, the ViewModel will not always need to change
The core of the mechanism is the {Binding ...} bit in the Xaml that tell WPF to synchronize the data between the Text property of the TextBox and the SomeText property of the object that is assigned to the DataContext.
The other significant bits are:
- in the constructor of the window the setting of the DataContext and
- in the ViewModel the raising of the PropertyChanged event when the SomeText property changes so the binding will be notified.
Note that this is just a basic example of DataBinding, there are many improvements that could be made in this code.
I can't figure out why the binding changes in my large project wont work. I have simplified it down to a sample project that still doesn't work. I would like to continue to set the datacontext the way I currently am if possible because that is how the other project does it. With the following code the text in SomeText is not showing up in the textbox. How do I fix this?
Code Behind:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
Data Class:
public class ViewModel
{
public string SomeText = "This is some text.";
}
Main User Control:
<UserControl xmlns:ig="http://schemas.infragistics.com/xaml" x:Class="XamGridVisibilityBindingTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:XamGridVisibilityBindingTest="clr-namespace:XamGridVisibilityBindingTest" mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<TextBox Text="{Binding SomeText}" BorderBrush="#FFE80F0F" Width="100" Height="50"> </TextBox>
</Grid>
</UserControl>
Edit: I am only trying to do one-way binding.
You need to use a property, and make your VM inherit from INotifyPropertyChanged and raise the PropertyChanged event whenever SomeText changes:
public class ViewModel : INotifyPropertyChanged
{
private string someText;
public event PropertyChangedEventHandler PropertyChanged;
public string SomeText
{
get { return someText; }
set
{
someText = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SomeText"));
}
}
}
public ViewModel()
{
SomeText = "This is some text.";
}
}
I figured it out, you can only bind to Properties!
public class ViewModel
{
public string SomeText { get; set; }
public ViewModel()
{
SomeText = "This is some text.";
}
}
I'm trying to create a user control with dependency properties to bind to. Internally I have a ComboBox that is bound to these same properties, but the binding only works one way. The ComboBox fills from the ItemsSource, but SelectedItem doesn't get updated back to the viewmodel I'm binding to.
A simplified example:
This is the view model to bind with the user control:
public class PeopleViewModel : INotifyPropertyChanged
{
public PeopleViewModel()
{
People = new List<string>( new [] {"John", "Alfred","Dave"});
SelectedPerson = People.FirstOrDefault();
}
public event PropertyChangedEventHandler PropertyChanged;
private IEnumerable<string> _people;
public IEnumerable<string> People
{
get { return _people; }
set
{
_people = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("People"));
}
}
}
private string _selectedPerson;
public string SelectedPerson
{
get { return _selectedPerson; }
set
{
_selectedPerson = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedPerson"));
}
}
}
}
This is the User control:
<UserControl x:Class="PeopleControlTest.PeopleControl"
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="56" d:DesignWidth="637">
<StackPanel >
<ComboBox Margin="11"
ItemsSource="{Binding BoundPeople, RelativeSource={RelativeSource AncestorType=UserControl}}"
SelectedItem="{Binding BoundSelectedPerson, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</StackPanel>
with code behind
public partial class PeopleControl : UserControl
{
public PeopleControl()
{
InitializeComponent();
}
public static readonly DependencyProperty BoundPeopleProperty =
DependencyProperty.Register("BoundPeople", typeof(IEnumerable<string>), typeof(PeopleControl), new UIPropertyMetadata(null));
public static readonly DependencyProperty BoundSelectedPersonProperty =
DependencyProperty.Register("BoundSelectedPerson", typeof(string), typeof(PeopleControl), new UIPropertyMetadata(""));
public IEnumerable<string> BoundPeople
{
get { return (IEnumerable<string>)GetValue(BoundPeopleProperty); }
set { SetValue(BoundPeopleProperty, value); }
}
public string BoundSelectedPerson
{
get { return (string)GetValue(BoundSelectedPersonProperty); }
set { SetValue(BoundSelectedPersonProperty, value); }
}
}
And this is how I bind the user control in the main window (with the windows data context set to an instance of the viewmodel)
<Window x:Class="PeopleControlTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:PeopleControlTest"
Title="MainWindow" Height="350" Width="525">
<Grid>
<controls:PeopleControl
BoundPeople="{Binding People}"
BoundSelectedPerson="{Binding SelectedPerson}"/>
</Grid>
</Window>
The combobox in the user control fills with the names, but when I select a different name this doesn't get updated back to the view model. Any idea what I'm missing here?
Thanks!
Some properties bind two-way by default (Including SelectedItem) but your BoundSelectedPerson does not. You can set the Mode of the binding:
<controls:PeopleControl
BoundPeople="{Binding People}"
BoundSelectedPerson="{Binding SelectedPerson, Mode=TwoWay}"/>
Or you can make it TwoWay by default by setting a flag on the DependencyProperty:
public static readonly DependencyProperty BoundSelectedPersonProperty =
DependencyProperty.Register("BoundSelectedPerson", typeof(string), typeof(PeopleControl), new FrameworkPropertyMetadata("",FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
Did this:
public class myClass : INotifyPropertyChanged
{
public bool? myFlag = false;
public bool? MyFlag
{
get { return myFlag; }
set
{
myFlag = value;
OnPropertyChanged("MyFlag");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
Declared a test variable myClass in the Window1 class:
public partial class Window1 : Window
{
myClass test;
public Window1()
{
InitializeComponent();
test = new myClass();
}
}
Here's an example XAML file:
<Window x:Class="WpfApplication5.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300" IsEnabled="{Binding ElementName=test, Path=MyFlag}">
<Grid>
<Button>You shouldn't be clicking me</Button>
</Grid>
</Window>
The window isn't disabled, and the debugger is showing me that message.
What am I missing?
The ElementName property of the Binding is meant to target other elements in xaml, not properties/fields of the object. The common way to do what you're trying to accomplish is to assign an instance of myClass to the Window's DataContext property:
public partial class Window1 : Window
{
//myClass test;
public Window1()
{
InitializeComponent();
this.DataContext = new myClass(); //test = new myClass();
}
}
And then your binding will look like this: IsEnabled="{Binding Path=MyFlag}".
If you actually wanted to bind to a property on the Window itself, you would use a binding like this:
IsEnabled="{Binding RelativeSource={RelativeSource Self}, Path=test.MyFlag}"
I have a custom base user control in silverlight.
<UserControl x:Class="Problemo.MyBaseControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Border Name="HeaderControl" Background="Red" />
</Grid>
</UserControl>
With the following code behind
public partial class MyBaseControl : UserControl
{
public UIElement Header { get; set; }
public MyBaseControl()
{
InitializeComponent();
Loaded += MyBaseControl_Loaded;
}
void MyBaseControl_Loaded(object sender, RoutedEventArgs e)
{
HeaderControl.Child = Header;
}
}
I have a derived control.
<me:MyBaseControl x:Class="Problemo.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:me="clr-namespace:Problemo"
d:DesignHeight="300" d:DesignWidth="400">
<me:MyBaseControl.Header>
<TextBlock Name="header" Text="{Binding Text}" />
</me:MyBaseControl.Header>
</me:MyBaseControl>
With the following code behind.
public partial class MyControl : MyBaseControl
{
public string Text
{
get; set;
}
public MyControl(string text)
{
InitializeComponent();
Text = text;
}
}
I'm trying to set the text value of the header textblock in the derived control.
It would be nice to be able to set both ways, i.e. with databinding or in the derived control code behind, but neither work. With the data binding, it doesn't work. If I try in the code behind I get a null reference to 'header'. This is silverlight 4 (not sure if that makes a difference)
Any suggestions on how to do with with both databinding and in code ?
Cheers
First of all I'll show you how to adjust your Derived control to handle this. You need to do two things, first you need to implement INotifyPropertyChanged and secondly you to add the binding to the user control.
MyControl Xaml:-
<me:MyBaseControl.Header>
<TextBlock Name="headerItem" />
</me:MyBaseControl.Header>
MyControl code:-
public partial class MyControl : MyBaseControl, INotifyPropertyChanged
{
public MyControl ()
{
InitializeComponent();
headerItem.SetBinding(TextBlock.TextProperty, new Binding("Text") { Source = this });
}
string _text;
public string Text
{
get { return _text; }
set { _text = value; NotifyPropertyChanged("Text"); }
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
#endregion
}
This should get you working. However, as soon as you feel you need to inherit a UserControl based class you should take a step back and ask whether the base and derived items ought to be templated controls instead. If I get time I'll try to add a version of your code in terms of templated controls.