How can I bind a Text property for my TextBox that read from a source but it will store its value to a different target?
Let's say
I have a textbox which is bond to a path in a CollectionViewSource
<Window>
<Window.Resources>
<CollectionViewSource Source="{Binding Source={StaticResource ProgramView}, Path='FK_LevelList_ProgramList'}" x:Key="LevelLookupView" />
</Window.Resources>
<TextBox Name="FeePerTermTextbox" Text="{Binding Source={StaticResource LevelLookupView}, Path='FeePerTerm', Mode=OneWay, StringFormat=c2}"/>
</Window>
When perform save, the value of the TextBox will store to another model that is different from the CollectionViewSource
Thanks
I consider this flawed. What happens if the source gets updated? Should the textbox be overwritten?
The reason for this design is imho, that the UI should reflect the "traits" of the element set as DataContext, therefore i expect it to contain the value i give in the model or in the ui. Now there is of course nothing stopping you from not writing the value in your viewmodel to your model, when receiving the set value from the textbox.
public class Redirecter
{
public string FileName
{
get{return mModel.FileName;}
set{mProxy.FileName = value;}
}
}
But this of course won't work well together with INotifyPropertyChanged. I would use a different approach. Use a model that reflects your ui more. If you open the view fill in this ui model with your settings from model A. If you now save this, save each property into Model B.
Related
I want to call controls inside view like button and item template inside viewmodel. Please tell how can I do that. My view contains following
<ItemsControl Name="cDetails"
Width="395"
ItemTemplate="{DynamicResource Test}"
ItemsSource="{Binding ViewModels}"
Visibility="{Binding IsLoaded,
Converter={StaticResource visibilityConverter}}">
<Button Name="btnComplete"
Grid.Column="1"
HorizontalAlignment="Center"
Command="{Binding AuditCommand}"
CommandParameter="1">
Complete
</Button>
Please tell how can I call these items in my viewmodel using vb.net.
Thanks
Accessing your view components from inside your viewmodel is not the way to do things in MVVM. Because it is specifically not designed to work this way, you will have to go out of your way to make it work. You should probably investigate how to accomplish your goals using MVVM properly, or forego using MVVM at all and do the work in your code-behind.
Since you have not described what your goal is, it is hard to provide specific recommendations. In general when using MVVM, you manipulate things in your viewmodel and set properties. Your view binds to these properties so that it updates appropriately as they are being set. Your viewmodel does not directly manipulate the views themselves, only the viewmodel properties that they are bound to.
For example, let's say you are updating the text on a TextBlock. You could do something like this in xaml:
<TextBlock Text="{Binding SomeText}" />
Then, your viewmodel (which should implement the INotifyPropertyChanged interface) defines this property and sets it as desired.
public string SomeText
{
get { return _someText; }
set
{
if (_someText != value)
{
_someText = value;
NotifyPropertyChanged("SomeText");
}
}
}
private string _someText;
...
// At any time, you can set the property, and the
// binding will update the text in the control for you.
SomeText = "Some text";
If you absolutely need to manipulate your views from code (or if you are not using MVVM), the appropriate place for that sort of code is the "xaml.cs" file next to your view (the code-behind). You can assign a name to anything in your xaml using syntax like <TextBlock x:Name="SomeTextBlock" /> and then access it from the code-behind as a member variable with the same name. For example, you could do SomeTextBlock.Text = "Some text". However, this is not usually necessary for the vast majority of use cases if you are using MVVM.
You shouldn't try to access controls directly from the ViewModel. The ViewModel must not know about the View implementation.
Instead, in WPF we connect View and ViewModel through Bindings. Bindings connect Properties of controls in the View, with Properties in the ViewModel.
Commands are a special type of Property that can be bound as actions for controls like Button.
In your example, you would need to have these properties in your ViewModel:
A collection named ViewModels
A boolean named IsLoaded
And an ICommand named AuditCommand
By managing those properties, you should be able to control what's shown in your View and its behavior.
If you need more control, create more Bindings to other properties, or create some events in your ViewModel and manage them from your View's code-behind.
WPF, MVVM
I'm finding that if I use a CollectionViewSource with my ComboBox, when I close the window, an extra call to the SelectedValue Setter is executing, if SelectedValue is bound to a string property. If I set the ItemsSource binding directly to the VM, this call does not happen. The extra call is causing values to change in the VM, resulting in incorrect data. I have other ComboBoxes setup the same way, but they bind to integer values.
CollectionViewSource definition:
<CollectionViewSource x:Key="AllClientsSource" Source="{Binding AllClients}" >
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="ClientName" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
ComboBox with CollectionViewSource:
<ComboBox Grid.Column="2"
ItemsSource="{Binding Source={StaticResource AllClientsSource}}"
DisplayMemberPath="ClientName" SelectedValuePath="ClientId"
SelectedValue="{Binding Path=ClientId}"
Visibility="{Binding Path=IsEditingPlan, Converter={StaticResource BoolVisibility}}" />
ComboBox direct to VM (Forgoing sorting):
<ComboBox Grid.Column="2" ItemsSource="{Binding AllClients}"
DisplayMemberPath="ClientName" SelectedValuePath="ClientId"
SelectedValue="{Binding Path=ClientId}"
Visibility="{Binding Path=IsEditingPlan, Converter={StaticResource BoolVisibility}}" />
Can anyone tell me why there is an extra setter call using the CollectionViewSource? What's different about the string binding? Is there a way to properly work around it?
EDIT: I tried changing it up and using the SelectItem property on the ComboBox. Same result. So it seems that if the item is a scalar data type, it works as expected. If it's an object, you get an extra setter call with a null value. Again, if I remove the CollectionViewSource from the equation, it works as expected.
EDIT, AGAIN: I added a link to a sample project that illustrates the issue. Targets .Net 4.5.
Run the project.
Click to display View One
Select a Client and the client's name will display on the right.
Click to display View Two
Go back to View One - Note that the selected client is no longer selected.
Click to display View Three
Select a Region and the region's name is displayed on the right.
Go back to View Two
Go back to View Three - Note that the selected region is still selected.
The only difference between the views is that One and Two use a CollectionViewSource. Three binds directly to the ViewModel. When you move to a new tab from One or Two, the setter for the selected item is getting called with a null value. Why? What's the best work-around?
Thanks.
Apparently this is caused when the CollectionViewSource is removed from the visual tree... I moved the CollectionViewSource to the ViewModel and exposed it as a property and the issue is effectively worked-around.
This is the scenario:
I have a telerik gridview on my page, this is bound to a PagedCollectionView
with items of class "GekoppeldeOntvangstRegel",
this class implements INotifyPropertyChanged
Several columns have a CellTemplate with a TextBlock bound to an object of this class, like this:
<TextBlock Text="{Binding ConverterParameter='aantal', Converter={StaticResource GekoppeldeRegelDecimalFormatConverter}, NotifyOnValidationError=True, ValidatesOnDataErrors=True, ValidatesOnExceptions=True, ValidatesOnNotifyDataErrors=True}" HorizontalAlignment="Right" ToolTipService.ToolTip="{Binding ConverterParameter='aantal', Converter={StaticResource GekoppeldeRegelToolTipDecimalFormatConverter}}" />
This converter converts the "Aantal" property to a string with a specific number of decimals.
When I update the "Aantal" property from code with the OnPropertyChanged("Aantal") of course the binding isn't updated (since the textblock is bound to the entire object, not the property) so the old value is still visible.
How can I refresh the column contents from my viewmodel or object when the property changes?
When I bind directly to the "Aantal" property everything works perfectly (besides the converter not being applied, which is necessary)
As a workaround I now have created several extra properties on the "GekoppeldeOntvangstRegel" class.
These properties call the converter and return the right value with the right number of decimals.
On these properties raising OnPropertyChanged does work to refresh the bindings.
I'm not really happy with this solution but it works for now.
My application has a couple of ObservableCollections, one which is nested within an element of the other. Each contain a number of fields e.g.
ObservableCollectionA (called Listings)
Title
Description
Address
Images As MediaItems
ObservableCollectionB (called MediaItems)
ImageName
Src
DisplayTime
Currently I have been accessing ObservableCollections as follows:
Listings(0).MediaItems(0).ImageName
My application has the main Window display the items from Listings and a UserControl which contains a ListBox which displays the items from MediaItems.
Currently my Window is bound to Listings using code in the New method:
Dim AppLocal As Program = Application.CurrentItem
AppLocal.CurrentItem = 0
Me.DataContext = Listings.Item(AppLocal.CurrentItem)
For the Listings ObservableCollection, the UserControl has a XAML DataContext which references a local method which pulls the records from the nested MediaItems ObservableCollection.
<UserControl.DataContext>
<ObjectDataProvider ObjectType="{x:Type local:ThumbImageLoader}" MethodName="LoadImagesv2" IsAsynchronous="True" />
</UserControl.DataContext>
<Grid x:Name="ThumbListBoxGrid">
<ListBox x:Name="ThumbListBox" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" />
</Grid>
The method is here:
Public NotInheritable Class ThumbImageLoader
Public Shared Function LoadImagesv2() As List(Of MediaItems)
Dim AppLocal As Program = Application.Current
Dim ThumbImages As New List(Of MediaItems)
ThumbImages = Listings(AppLocal.CurrentItem).MediaItems
Return ThumbImages
End Function
End Class
Whilst developing the UI layout I have just been binding the first item (0 index). I now want to be able to set AppLocal.CurrentItem from anywhere in the application so the Window and the ListBox are updated.
Ideally I would like it so when the global property index changes, the UI is updated.
How do I do this?
Is there a better way to go about it?
Ben
Ok, I discovered the joy of CollectionView. Offered exactly what I was after and was excrutiatingly easy to implement. I was blown away at not only how easy it was to implement, but I managed to cut out more lines of code than I used to implement it.
I implemented a public CollectionViewSource
Public ListingDataView As CollectionViewSource
In my main Window, I implemeted it as follows:
<CollectionViewSource x:Key="ListingDataView" />
and bound my top-level Grid to it:
<Grid DataContext="{Binding Source={StaticResource ListingDataView}}">
In my Application Startup I set the CollectionView Source
AppLocal.ListingDataView = CType(Application.Current.MainWindow.Resources("ListingDataView"), CollectionViewSource)
AppLocal.ListingDataView.Source = Listings
The next part which impressed me the most was implementing it for my User Control. I remembered the UserControl is inheriting from the main window so it has access to the CollectionView already, so I ditched the separate Class and Method binding in favour for this:
<ListBox ItemsSource="{Binding Path=MediaItems}" VerticalAlignment="Top" IsSynchronizedWithCurrentItem="True" />
Now whene I want to set the Current List Index, I simply call this:
AppLocal.ListingDataView.View.MoveCurrentToPosition(AppLocal.CurrentProperty)
A few milliseconds later, the UI updates automatically.
Done!!
When you want multiple source data (like your observable collection properties and the index for the observable collection) to a single target you should use MultiBinding.
So in your case somethign like this should help...
<ListBox x:Name="ThumbListBox" IsSynchronizedWithCurrentItem="True" >
<ListBox.ItemsSource>
<MultiBinding Converter="{StaticResource CollectionAndIndexCollaborator}">
<Binding Path="Listings" />
<Binding Path="Application.CurrentItem.CurrentItem" />
</MultiBinding>
</ListBox.ItemsSource>
</ListBox>
provided that ....
Your data context is some class that holds the Application object via a property of same name Application and the Listings collection via property of same name Listings.
Your DataContext class and Application class must have INotifyPropertyChanged implemented. It should also raise notifications for Application and Setter of CurrentItem and CurrentItem.CurrentItem properties.
CollectionAndIndexCollaborator.Convert() method returns the same indexed value as the final collection....
return ((ObservableCollection)values[0]).Item(int.Parse(values[1])) ;
where assuming MyListingType is the T of your Listings collection.
This way when anyone changes the global Application.CurrentItem.CurrentItem the multi binding above will get notified and will select the required Listings item.
I understand that Silverlight 3.0 has binding but just want a simple example on how to use this to read a property from a class.
I have a class called Appointment which as a String property called Location:
Public Property Location() As String
Get
Return _Location
End Get
Set(ByVal Value As String)
_Location = Value
End Set
End Property
With a Private Declaration for the _Location as String of course.
I want a XAML element to bind to this property to display this in a TextElement, but it must be in XAML and not code, for example I want something like this:
<TextBlock Text="{Binding Appointment.Location}"/>
What do I need to do to get this to work?
It has to be a Silverlight 3.0 solution as some WPF features are not present such as DynamicResource which is what I'm used to using.
Just to add that my XAML is being loaded in from a seperate XAML File, this may be a factor in why the binding examples don't seem to work, as there are different XAML files the same Appointment.Location data needs to be applied.
You have two options.
If the "Appointment" class can be used as the DataContext for the control or Window, you can do:
<TextBlock Text="{Binding Location}" />
If, however, "Appointment" is a property of your current DataContext, you need a more complex path for the binding:
<TextBlock Text="{Binding Path=Appointment.Location}" />
Full details are documented in MSDN under the Binding Declarations page. If neither of these are working, make sure you have the DataContext set correctly.
You need something in code, unless you want to declare an instance of Appointment in a resource and bind to that but I doubt thats what you want.
You need to bind the Text property to the Property Path "Location" then assign the DataContext of the containing XAML to an instance of the Appointment:-
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock Text="{Binding Location}" />
</Grid>
Then in the control's load event:-
void Page_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = new Appointment() { Location = "SomePlace" };
}
Note in this case I'm using the default Page control.
If I'm reading correctly, you need to create an instance of Appointment, set the DataContext of the control to that instance and modify your binding to just say: Text="{Binding Location}"
Also, consider implementing INotifyPropertyChanged on your Appointment class to allow the data classes to notify the UI of property value changes.