I have a following wpf i need to fill my datagrid rows from text box values when user enters values in textboxes and press Add Vlan button.I am using MVVM pattern so i created an ICommand interface behind it:
My C# code is :
#region ICommand
public ICommand AddVlan
{
get
{
if (_addVlan == null)
_addVlan = new RelayCommand(() => this.AddVlans());
return _addVlan;
}
}
public ICommand RemoveVlan
{
get
{
if (_removeVlan == null)
_removeVlan = new RelayCommand(() => this.RemoveVlans());
return _removeVlan;
}
}
#endregion //ICommand region end
The AddVlans() method will have actual event performer but i dont know how to perform event for displaying data in datagrid.
Xaml code is :
<Button Grid.Column="3"
Grid.Row="1"
Content="Add VLAN"
Margin="10,5,0,0"
Style="{StaticResource AppButtons}"
Command="{Binding AddVlan}"
/>
<Button Grid.Column="3"
Grid.Row="2"
Content="Remove VLAN"
Margin="10,5,0,0"
Style="{StaticResource AppButtons}"
Width="100"
Command="{Binding RemoveVlan}"
/>
<DataGrid Grid.Row="4"
Grid.ColumnSpan="3"
Height="200"
Margin="10,10,0,0">
<DataGrid.Columns>
<DataGridTextColumn Header="VLAN Name" />
<DataGridTextColumn Header="VLAN ID" />
<DataGridTextColumn Header="IP" Width="100"/>
<DataGridTextColumn Header="VLAN Ports" Width="100"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
Can anyone explain me how to do this ??
Implement INotifyPropertyChanged properly, create properties for every TextBox/Field/ComboBox and bind them TwoWay. Make sure the changes made in your GUI are successfully reflected to your ViewModel. That being said, you are able to control the data that the user of your application has put in.
You will need to place an ObservableCollection in your ViewModel bound to your DataGrid's ItemsSource in order to notify the View about the changes. In your AddVlans() function, simply add a new entry to this ObservableCollection by using the associated property and you are done. If you implemented INotifyPropertyChanged properly, the View should get notified about your added entry and therefore the entry would show up in your DataGrid.
If you don't know yet what I'm talking about, please consider reading about Binding in WPF first before continuing.
Related
I'm trying to extend the app from a WPF MVVM tutorial as an exercise. I've found no solution on the net for this specific problem I'm facing here.
I have a ViewModel with an ObservableCollection called "StudentsToAdd". This collection is bound to an ItemsControl. Outside the ItemsControl I have a Button with a binding to the "AddCommand" command in the ViewModel. The relevant extract form my XAML looks as follows:
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Button Content="Add" Command="{Binding AddCommand}" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75"/>
<Button Content="+" Command="{Binding AddToAddListCommand}" HorizontalAlignment="Center" VerticalAlignment="Center" Padding="3,0,3,0" Margin="50,0,0,0"/>
<Button Content="-" Command="{Binding RemoveFromAddListCommand}" HorizontalAlignment="Center" VerticalAlignment="Center" Padding="5,0,5,0" Margin="5,0,0,0"/>
</StackPanel>
<ItemsControl x:Name="AddList" ItemsSource="{Binding Path=StudentsToAdd}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Path=FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="100" Margin="0 5 3 5">
<TextBox.InputBindings>
<KeyBinding Command="{Binding ElementName=AddList, Path=DataContext.AddCommand}" Key="Return"/>
</TextBox.InputBindings>
</TextBox>
<TextBox Text="{Binding Path=LastName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="100" Margin="0 5 3 5">
<TextBox.InputBindings>
<KeyBinding Command="{Binding ElementName=AddList, Path=DataContext.AddCommand}" Key="Return"/>
</TextBox.InputBindings>
</TextBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
The + and - buttons will add or remove students from the StudentsToAdd collection. The "AddCommand" moves all entries from StudentsToAdd to another collection called "Students" when executed.
Now what I can't get to work is this: whenever a Student in StudentsToAdd is modified (after any keystroke: UpdateSourceTrigger=PropertyChanged). I want the Add Button to evaluate the CanExecute of AddCommand in the ViewModel so its IsEnabled property is automatically set accordingly. The command methods in the ViewModel currently look as follows:
private void OnAdd()
{
foreach (Student s in StudentsToAdd)
{
Students.Add(s);
}
StudentsToAdd.Clear();
StudentsToAdd.Add(new Student { FirstName = string.Empty, LastName = string.Empty });
}
private bool CanAdd()
{
if (StudentsToAdd != null && StudentsToAdd.Count > 0)
{
return StudentsToAdd.All(x => !string.IsNullOrWhiteSpace(x.FirstName) && !string.IsNullOrWhiteSpace(x.LastName));
}
return false;
}
Does anybody know how I can achieve this without coupling parts of the MVVM?
Does anybody know how I can achieve this without coupling parts of the MVVM?
There's not much context in your question. But, it seems that you are asking how to accomplish this entirely within the view model/model layer, without involving the view layer. At least, that's what you should be asking.
If so, it should be relatively simple, assuming the code you didn't show is written reasonably. That is, since your CanAdd() method depends on property values of the Student objects, you'll need to subscribe to the Student.PropertyChanged event, and raise the ICommand.CanExecuteChanged event any time any of the Student objects' PropertyChanged event is raised.
For what it's worth, I would also encapsulate the "can be added" logic in the Student class, rather than the ViewModel class. Expose that state as a single property that the ViewModel class can check. This will address a couple of things:
Your Student class seems like the more logical place to put code that determines whether the class is ready to be added to a list of Students, and
The ViewModel class can check to make sure it's that property that is changing, so it doesn't bother to go to all the work to check all the other Student objects every time any Student property changes, and each Student object will effectively be caching the "can be added" value, so that that work to check all the other Student objects is a simple property retrieval, instead of having to re-evaluate the state every single time.
I assume you already understand how to raise the ICommand.CanExecuteChanged event, but if not, here are a couple of posts that should help you with that:
CanExecuteChanged event of ICommand
ICommand CanExecuteChanged not updating
(You'll see that there are two basic strategies: implement something in the ICommand object that will explicitly raise the ICommand.CanExecuteChanged event, or call InputManager.InvalidateRequerySuggested() to force the Input Manager to call all the CanExecute() methods it knows about. IMHO, the latter is a pretty heavy-weight and less-desirable approach, hence my suggestion to use the ICommand.CanExecuteChanged event.)
I am using the MVVM pattern to write a WPF application. I created the UI using a TabControl. In one tab I have a list of clients in my company and in the other tab I have a form which is used to add new clients. I want two things:
Adding new client --> new position on the list when the tab with clients is pressed
Clearing form TextBoxes after adding a client
Neither of them works.
My DataGrid part:
<StackPanel DataContext="{StaticResource ClientsVM}">
<Image HorizontalAlignment="Left" VerticalAlignment="Top" Source="pack://application:,,,/Insurance company;component/Resources/logo.png" Height="100" Margin="5,15,0,0"/>
<DataGrid Name="ClientsGrid" ItemsSource="{Binding Clients, Mode=TwoWay}" IsReadOnly="True" Margin="130,0" AutoGenerateColumns="False" ColumnWidth="101">
<DataGrid.Columns>
<DataGridTextColumn Header="Client ID" Binding="{Binding ClientId}"/>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Surname" Binding="{Binding Surname}"/>
<DataGridTextColumn Header="PESEL" Binding="{Binding PESEL}" />
</DataGrid.Columns>
<DataGrid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding ClientsGridLeftDoubleClickCommand}" CommandParameter="{Binding ElementName=ClientsGrid, Path=SelectedItem}" />
</DataGrid.InputBindings>
</DataGrid>
</StackPanel>
Part of my ViewModel:
private ObservableCollection<ClientSet> _clients;
public ObservableCollection<ClientSet> Clients
{
get { return _clients; }
set
{
if (_clients != value)
{
_clients = value;
RaisePropertyChanged(() => "Clients");
}
}
}
Unfortunately, doing something like:
_clients = new ObservableCollection<ClientSet>(updatedListOfClientsHere);
doesn't work. Why not?
The other thing is with clearing the form. It looks like this:
<Label Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="100" FontSize="15">Surname</Label>
<TextBox Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="141" Name="Surname" Text="{Binding Client.Surname, Mode=TwoWay, ValidatesOnDataErrors=True}"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="100" FontSize="15">Name</Label>
<TextBox Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="141" Name="Name" Text="{Binding Client.Name, Mode=TwoWay, ValidatesOnDataErrors=True}" />
And the values typed by the user are properly reflected in the ViewModel class. But if I clear them in the ViewModel - nothing happens in the UI. Part of my ViewModel here:
private ClientSet _client;
public ClientSet Client
{
get { return _client; }
set
{
if (value != _client)
{
_client = value;
RaisePropertyChanged(() => "Client");
};
}
}
// Some code
// Clearing the form:
_client = new ClientSet(); // This shouldn't work?
Client.Name = string.empty; // This should work!!!
I am really out of ideas right now.
I have noticed a few problems from your question. First, calling the private member of any property in a view model should only be done when you don't want the UI to update. If you want the UI to update, then you have to use the public properties, because that is what notifies the INotifyPropertyChanged interface (through your RaisePropertyChanged method). So to clear the client list, just call:
Clients = new ObservableCollection<ClientSet>(updatedListOfClientsHere);
Next, I don't know for sure because you didn't show your ClientSet class, but I'm guessing that you didn't implement the INotifyPropertyChanged interface in it. However, you are still expecting property changes of that class to be updated... they won't unless you either implement it in that class, or wrap each property in your view model and call your RaisePropertyChanged method from there. Then, calling Client.Name = string.empty; will clear that field.
I'm not entirely sure what you're asking, but lets see:
First, with your observable collection, you don't need to set it again. If you create a new Client, all you need to do is :
Clients.Add(new_client_object);
That's all. Otherwise, if it takes a while to create the clients, your program will get slower (I've seen this happen, trust me ...)
If I understand correctly, you want to clear the items in the add new client after you add it. All you need to do here is make sure the content control (hopefully your add client view is a usercontrol or something like that) receives an object of type client, and after you add it, just set it to null, or put a new (empty) object, or set them manually to empty strings (Be sure to have a a backup in case you want to cancel, or that you don't overwrite your new client fields).
You could add some screenshots to clarify what you want/need.
I am making the UIs for entering master data for various business entities (customer, etc). I run into this need to group together a TextBlock and a TextBox together frequently. i.e.
<Label Content="Civil Status:" Grid.Column="0" Grid.Row="1" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="3" Name="civilStatusTextBox" Text="{Binding Path=CivilStatus, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
<Label Content="Company:" Grid.Column="0" Grid.Row="2" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="3" Name="companyTextBox" Text="{Binding Path=Company, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
Is there any way to do this with less typing? i.e.
<custom:LabeledTextBox Label="Civil Status:" Text="{Binding Path=CivilStatus, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" ... />
Or possibly, any libs which offer something like this?
EDIT : Forget the container Grid for a moment and assume it is a StackPanel.
Here's a step-by-step solution that I managed to put together. To set the stage, I'm going to assume we've got a very simple UserControl that has the following XAML content.
<UserControl x:Class="WpfApplication2.UserControl1" [ ... auto gen code removed ... ] >
<TextBox MinWidth="50" x:Name="TBox" />
</UserControl>
From an XAML that uses our UserControl we'd essentially want to set a data binding for the Text property on TBox. Idealy we could use a plain syntax like:
<local:UserControl1 TBox.Text="{Binding ...}" />
unfortunately I don't know any XAML syntax that would allow targeting an sub-element's property, so the next best thing would be to introduce a "staging" property in the UserControl itself and bind through that.
Since Binding only works for Dependency properties, the property we'll introduce needs to be a DependencyProperty. We'll also bind the Text property of TBox straight to our DependencyProperty from code.
The code-behind for the UserControl looks like this:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
// Set binding from code
this.TBox.DataContext = this;
this.TBox.SetBinding(TextBox.TextProperty, new Binding { Path = new PropertyPath("TBValue"), Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });
}
public static readonly DependencyProperty TBValueProperty = DependencyProperty.Register("TBValue", typeof(string), typeof(UserControl1));
public string TBValue
{
get { return this.GetValue(TBValueProperty) as string; }
set { this.SetValue(TBValueProperty, value); }
}
}
}
With this in place we can use the UserControl like this, binding to the TBValue property:
<local:UserControl1 TBValue="{Binding Path=Test, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
What you want to achieve (Master-Detail-Views) is actually well supported by Visual Studio out of the box. Open the following Menu structure : Project -> Add Data Source, then choose data source type Object. In the following, select the classes that you want to generate input fields for and finish the wizard.
Then, if not already open, open up your Data Sources tool window (Shift+Alt+D). You should see a DataSource of the type you just generated. To get a labelled field for each property of the object, open the source dropdown and click Details.
Note that the properties have such dropdowns as well, so that you can choose how you want to edit them (ComboBox, TextBox, Custom, no editor,...).
Now just drag the DataSource onto your window. You will get a Grid that's filled with all the labels and editors you desired. DataBinding and validation is also supported right away, so all you will have to do is set the generated Grid's DataContext.
Hope this saves you some work.
P.S. The screenshots are made in my german VS instance, still I thought they might help you identify the right dialogues / windows.
WPF MVVM in ViewModel I want to access the same data a textbox in the XAML is bound to
The XAML on MainWindow.xaml has a textbox bound to StoredProcs/ProcName
<TextBox Name="txtProcName" Text="{Binding Path=StoredProcs/ProcName}"></TextBox>
And a Grid bound to StoredProcs
Whenever the grid selection changes, the bound text in the textbox changes as it should.
<DataGrid AutoGenerateColumns="False"
Height="300" Width="290"
HorizontalAlignment="Center"
Name="dataGrid1"
VerticalAlignment="Top"
ItemsSource="{Binding StoredProcs}"
IsSynchronizedWithCurrentItem="True"
Margin="-6,0" Grid.RowSpan="2" Grid.Row="0">
<DataGrid.Columns>
<DataGridTextColumn Header="Proc Name" Binding="{Binding ProcName}" >
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
I have a button that executes a procedure in MainWindowViewModel when clicked, that works
<Button Content="Create RDL" Command="{Binding CreateStoredProcedure}" />
In the CreateStoredProcedure code, I need to access the same data that is displayed in the textbox (not using code behind). I would think I need to get the StoredProcs/ProcName but can't figure out how to do that.
I tried adding CommandParameter to the XAML but don't know how to access it in the CreateStoredProcedure instructions as it won't allow me to add paramaters
void CreateStoredProcedureExecute()
{
string procName = "proc";
//procName = { StoredProcs/ProcName };
MessageBoxResult result =
MessageBox.Show(String.Format("Create Stored Procedure {0}", procName));
}
bool CanCreateStoredProcedure()
{
return true;
}
public ICommand CreateStoredProcedure
{
get
{
return new RelayCommand(CreateStoredProcedureExecute,
CanCreateStoredProcedure);
}
}
Unless I am misunderstanding your question, you should be able to get the value of the property that the TextBox is bound to from within CreateStoredProcedure.
One thing though, if you want the TextBox to update the property, make sure you add "Mode=TwoWay" to your binding expression:
<TextBox Name="txtProcName" Text="{Binding Path=StoredProcs/ProcName, Mode=TwoWay}"></TextBox>
Unless I misunderstood I think you want something like this?
<Button
Content="Create RDL"
Command="{Binding CreateStoredProcedure}"
CommandParameter="{Binding ElementName=txtProcName, Path=Text}"/>
However as the other answer states, you should be able to just access the property in the ViewModel that is backing the textbox from the command, but if for some reason you cannot my code should work as well.
(Assuming you are defining RelayCommand as defined by this MSDN article, this should fix your other problem)
public ICommand CreateStoredProcedure
{
get
{
return new RelayCommand<object>(
(object parameter) => CreateStoredProcedureExecute(parameter),
(object parameter) => CanCreateStoredProcedure);
}
}
private void CreateStoredProcedureExecute(object parameter)
{
string ProcName = parameter as string;
}
I will admit my somewhat inexperience with setting up commands like this, but I did find a working example in my code that followed this, so hopefully it helps.
I think KDiTraglia has the right solution. The only thing I would do differently is to bind the CommandParameter to the model, not the UI element.
<Button
Content="Create RDL"
Command="{Binding CreateStoredProcedure}"
CommandParameter="{Binding Path=StoredProcs/ProcName}" />
I'm assuming that StoredProcs/ProcName is a placeholder for a real, valid binding path.
Root around here for more information: http://msdn.microsoft.com/en-us/library/ms752308
How can I bind event handler to the custom object that i created?
here is my XAML
<ListBox x:Name="ListData">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,30">
<Image VerticalAlignment="Top" HorizontalAlignment="Left" Source="{Binding Path=TileImage}" Width="175" Height="175" />
<TextBlock Margin="5" Width="200" TextWrapping="Wrap" VerticalAlignment="Top" HorizontalAlignment="Left" Text="{Binding Path=TileName}" FontSize="25"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code Behind
// Create collection
ImageTiles = new ObservableCollection<ImageTile>();
// Create each object in the collection
ImageTile RSS= new ImageTile("RSS", "/Images/Hard.jpg");
ImageTile test= new ImageTile("test", "/Images/Hard.jpg");
ImageTile Exam= new ImageTile("Exam", "/Images/Hard.jpg");
ImageTile Settings = new ImageTile("Settings", "/Images/Hard.jpg");
ImageTiles.Add(RSS);
ImageTiles.Add(test);
ImageTiles.Add(Exam);
ImageTiles.Add(Settings);
this.ListData.ItemsSource = ImageTiles;
I would like to bind the event handler along with each ImageTile. Any idea how to do so? =)
Based on your code structure I'll answer assuming you're not using MVVM or the like, however I'd definitely recommend that pattern for Silverlight development.
Nevertheless, your datasource binding would be something like:
<ListBox x:Name="ListData" ItemsSource="{Binding ImageTiles}">
</ListBox>
You could create a single generic click handler in your code behind and assign the Image's click event to that handler.
<ImageButton VerticalAlignment="Top" HorizontalAlignment="Left" Source="{Binding Path=TileImage}" Width="175" Height="175" Click="imageButton_Click" />
You could then have a method on your object responsible for redirecting to whatever necessary place for that particular image tile. Then in your code behind handler would resemble this:
private void imageButton_Click(object sender, RoutedEventArgs e)
{
var imageTile = ((ImageButton)sender).DataContext as ImageTile;
imageTile.RedirectSomewhere();
}
In your ViewModel, add a property to capture selected item (e.g.)
private ImageTile _selectedItem;
public ImageTile SelectedItem
{
get {return _selectedItem;}
set
{
if(value != _selectedItem)
{
_selectedItem = value;
RaisePropertyChanged("SelectedItem");
}
}
}
Then, in your XAML, bind the SelectedItem:
<ListBox ItemsSource="{Binding ImageTiles}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
</ListBox>
Use MVVM Light (or some other way) to bind the SelectionChanged event to a command, or wire up an event handler for the SelectionChanged event. In your Command (or event handler), get SelectedItem, and look at the first property (you didn't tell us what it was called, so I don't know what to call it).
Remember to make sure that SelectedItem is not null before you do anything, and, when you're done handling the command, set SelectedIndex to -1, so that they can select the same item twice, and still get the functionality to execute.