I'm still learning WPF and have searched for a way to do this that is not beyond my level of learning, but have not found an answer. Hopefully someone can help me out!
The purpose of my application was to be a simple check-in/check-out program. In my main application I list employees by name along with 2 buttons (in and out) as well as a comment field.
So far I've managed to open a new window when the 'In' or 'Out' buttons are clicked and prompt the user for a comment. I've managed to pass that comment back to my MainWindow, but now I am at a loss as to how to display that in the GridView.
Here is what the MainWindow.cs looks like:
private void menuIn_Click(object sender, RoutedEventArgs e)
{
var item = (sender as FrameworkElement).DataContext;
int rowNumber = lvUsers.Items.IndexOf(item) + 1;
// MessageBox.Show(String.Format("Button row is {0}", rowNumber));
string userCommentString = "";
SubWindow subWindow = new SubWindow(userCommentString, rowNumber);
subWindow.Show();
}
So the 'rowNumber' is the row that contains the comment field I want to change. The 'userCommentString' is what I want to go into that row. Is there not a simple way to insert a string into the grid if I know the row and column number?
Here is a small example how you could do that. Beside this, I would also recommend to learn MVVM.
XAML:
<ListView x:Name="lvUsers">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding Path=Name}" />
<GridViewColumn Header="Comment" Width="150" DisplayMemberBinding="{Binding Path=Comment}" />
<GridViewColumn Header="Button">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Margin="6,2,6,2">
<Button Content="Click" Click="Button_Click" />
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Add items to ListView
lvUsers.Items.Add(new Test() { Name = "User A" });
lvUsers.Items.Add(new Test() { Name = "User B" });
lvUsers.Items.Add(new Test() { Name = "User C" });
Manipulate row on button click:
private void Button_Click(object sender, RoutedEventArgs e)
{
//Get row number
var item = (sender as FrameworkElement).DataContext;
int rowNumber = lvUsers.Items.IndexOf(item);
//Use row number to manipulated the right row
//This should be done after comment is passed back to MainWindow
Test t = (Test)lvUsers.Items[rowNumber];
t.Comment = "new comment";
lvUsers.Items.Refresh();
}
Test class:
public class Test
{
public string Name { get; set; }
public string Comment { get; set; }
}
Related
The source code can be found here : https://www.codeproject.com/Articles/24973/TreeListView
The way the original author has set it up, is the data is filled in the xaml itself. I need to create the TreeViewList inside of my ViewModel, but I can't figure out how to bind my own TreeViewList within the xaml to display it properly.
Here's an example of me creating a tree in the code behind and calling the window.
public TreeListView TreeList { get; set; } = new TreeListView();
private void generateTree()
{
TreeList.Columns.Add(new GridViewColumn() { Header = "Col1" });
TreeList.Columns.Add(new GridViewColumn() { Header = "Col2" });
TreeList.Columns.Add(new GridViewColumn() { Header = "Col3" });
}
public ICommand AssemblyTreeCommand => new RelayCommand(AssemblyTree, p => CanAssemblyTree);
public bool CanAssemblyTree { get; set; } = true;
private void AssemblyTree(object parameter)
{
generateTree();
AssemblyTreeDialogWindow dialog = new AssemblyTreeDialogWindow()
{
DataContext = this,
Topmost = true
};
dialog.ShowDialog();
}
AssemblyTreeDialog Window class looks like this:
<local:TreeListView AllowsColumnReorder="True" ItemsSource="{Binding TreeList}">
<!--Create an item template to specify the ItemsSource-->
<local:TreeListView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}" />
</local:TreeListView.ItemTemplate>
<local:TreeListView.Columns>
<!--Create the first column containing the expand button and the type name.-->
<GridViewColumn Header="Name" Width="200">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!--The Expander Button (can be used in any column (typically the first one))-->
<local:TreeListViewExpander/>
<!--Display the name of the DataElement-->
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<!--Create a second column containing the number of children.-->
<GridViewColumn Header="Children" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<!--Display the size of the DataElement-->
<TextBlock Text="{Binding Children.Count}" HorizontalAlignment="Right"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<!--Create a third column containing the brush of the material.-->
<GridViewColumn Header="Brush" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!--Border showing the actual color-->
<Border Background="{Binding Brush}" CornerRadius="2"
Width="16" Height="16"
BorderThickness="1" BorderBrush="DarkGray"/>
<!--Display the brush-->
<TextBlock Text="{Binding Brush}"/>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</local:TreeListView.Columns>
<!--Create some sample data-->
<MaterialGroup>
<MaterialGroup>
<DiffuseMaterial Brush="Blue"/>
<DiffuseMaterial Brush="Red"/>
<SpecularMaterial Brush="Orange"/>
</MaterialGroup>
<EmissiveMaterial Brush="AliceBlue"/>
</MaterialGroup>
</local:TreeListView>
Interestingly if I bind the line <GridViewColumn Header="Name" Width="200"> so that it reads <GridViewColumn Header="{Binding TreeList}" Width="200">it gives me this:
I'll explain my end goal as best as possible.
The System is a giant list of parts. A main table displays all of the parts while a subtable displays all of the parts which make up that part. All parts (including those which are used to create other parts) exist within the MainTable. So a Parent part might have a set of children parts, which each individually have children parts which they are made up of. This is the relationship i'm trying to model using this tool.
The code that I've written maps the parts list out into a list of class objects which contain the data. I'll post it below. It's working to map to a TreeView right now.
A datastructure I've based it off is here : Treeview with multiple columns
private void generateTree(string PN)
{
Proces selectedRow = new Proces() { procesId = (int)Vwr.Table.SelectedRow.Row["PID"], procesName = (string)Vwr.Table.SelectedRow.Row["PN"], subProcesses = generateSubtable(PN) };
processes.Add(selectedRow);
}
public List<Proces> generateSubtable(string PN)
{
List<Proces> subTable = new List<Proces>();
foreach (DataRow mplrow in Vwr.Table.Tbl.Rows) if (mplrow["PN"].ToString() == PN)
MainVM.Modules.AllModules[0].SubVwr.Tables[0].LoadTableQuery.Prms[0].Val = mplrow[0];
MainVM.Modules.AllModules[0].SubVwr.Tables[0].Tbl = Db.GetTable(MainVM.Modules.AllModules[0].SubVwr.Tables[0].LoadTableQuery);
foreach (DataRow sub in MainVM.Modules.AllModules[0].SubVwr.Tables[0].Tbl.Rows)
{
Proces subItem = new Proces() { procesId = (int)sub["ItemNo"], procesName = sub["PN"].ToString(), subProcesses = generateSubtable(sub["PN"].ToString()) };
subTable.Add(subItem);
}
return subTable;
}
Found the answer! After some pretty extensive searching, and trying many different solutions. Thought i'd post incase someone else might also be trying to do the same.
credit:
http://dlaa.me/blog/post/9898803
I can't seem to get this to work. Anyone have any ideas why?
Here is the markup:
<ListView Width="210" Height="83" Margin="0 0 5 0" Name="FiltersListView">
<ListView.View>
<GridView>
<GridViewColumn Header="Column" Width="Auto" DisplayMemberBinding="{Binding FilterColumn}"></GridViewColumn>
<GridViewColumn Header="Rule" Width="Auto" DisplayMemberBinding="{Binding FilterRule}"></GridViewColumn>
<GridViewColumn Header="String" Width="Auto" DisplayMemberBinding="{Binding FilterString}"></GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Here is the window initializer:
public SelectionWindow()
{
InitializeComponent();
rows = new List<Row>();
// Create a new binding object and set the binding of this list view to it.
Binding myBinding = new Binding();
myBinding.Source = rows;
FiltersListView.SetBinding(ItemsControl.ItemsSourceProperty, myBinding);
}
Here is my rows object and class:
List<Row> rows;
public class Row
{
public string FilterColumn;
public string FilterRule;
public string FilterString;
}
But when I click this button, I don't see it getting added to the list:
private void AddButtonClick(object sender, RoutedEventArgs e)
{
Console.WriteLine("Adding row.");
rows.Add(new Row { FilterColumn = "1", FilterRule = "2", FilterString = "3" });
Console.WriteLine("Row added.");
}
This is because List<int> doesn't implement INotifyCollectionChanged, so the control doesn't know that the list has actually been updated.
Try to make rows an ObservableColelction<Row>.
Edit: as the OP mentioned, the other problem (empty rows) was due to using fields, not properties, on Row.
I have a Datagrid as follows
<DataGridTextColumn Header="Amount" CellStyle="{StaticResource RightAlignedCellStyle}" Binding="{Binding AmountToAllocate, UpdateSourceTrigger=LostFocus, StringFormat='{}{0:#,0.00}'}" />
<DataGridTemplateColumn Header="Comment" Width="*" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Comment}" TextWrapping="WrapWithOverflow"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="Add This Allocation" Command="ac:PICommands.AddAllocation" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
You will see that the ItemSource of the Grid is the property RenewalRows which is an ObservableCollection as follows:-
public ObservableCollection<Data.Payment.UnitRenewalsRow> RenewalRows
{
get
{
return renewalRows;
}
set
{
renewalRows = value;
OnPropertyChanged("RenewalRows");
}
}
SelectedItem is bound to the SelectedRenewalRow property as follows:-
public Data.Payment.UnitRenewalsRow SelectedRenewalRow
{
get
{
return renewalsRow;
}
set
{
renewalsRow = value;
//FullAllocationAmount();
OnPropertyChanged("SelectedRenewalRow");
}
}
There is a button in the last column which calls a command. The code behind as follows:-
private void Allocate(object sender, ExecutedRoutedEventArgs e)
{
ap.AddAnAllocation();
}
ap is the StaticResource class referred in the DataGrid ItemsSource="{Binding Source={StaticResource AllocatePaymentClass}, Path=RenewalRows}”
The code is as follows:-
public void AddAnAllocation()
{
SelectedRenewalRow.Outstanding = SelectedRenewalRow.Outstanding + SelectedRenewalRow.AmountToAllocate;
Allocation allocation = new Allocation();
allocation.Amount = SelectedRenewalRow.AmountToAllocate;
allocation.PaymentInfo = Payment;
allocation.RenewalInfo = SelectedRenewalRow;
allocation.Propref = PropRef;
allocation.FullAddress = FullAddress;
Allocations.Add(allocation);
Payment.Allocations = Allocations;
//reset
SelectedRenewalRow.AmountToAllocate = 0;
}
My problem is the last line. As soon as the user clicks the button that calls AddAnAllocation() I would like the screen to immediately update the selected row in the DataGrid with the AmountToAllocate Property to show as zero. The property is a field on the items in the RenewalRows property shown above. The screen eventually does update but only after the row has been deselected and then reselected and then sometimes even only if a few seconds have gone by.
Any ideas? If you need any further information or code please feel free to ask.
Im not sure if this is any help but it seems using DataTables + DataRows may have caused issues with other people in the past.
Have a look here
and here
Alright I tried my best but looks like I need help. I have a textbox, a listview and a button in my xaml file. Listview has two columns: Devicename and DeviceAddress. I have done a binding of both the listview and textbox in such a way, that whenever I select an item in listview(I2CDeviceList), the deviceaddress(2nd Column) gets displayed in my textbox.
XAML:
<TextBox PreviewTextInput="AddressBox_PreviewTextInput" Name="AddressI2C" Text="{Binding SelectedItem.I2CDeviceAddress, Path=AddressMessage, Mode=TwoWay, ElementName=I2cDeviceList}" />
<Button Content="I2C Read" Command="{Binding Path=I2CReadCommand}" Name="button9" />
<ListView Grid.Column="0" ItemsSource="{Binding I2CDeviceList}" SelectedItem="{Binding SelectedI2CDeviceList, Mode=TwoWay}" Height="100" HorizontalAlignment="Stretch" Name="I2cDeviceList" VerticalAlignment="Stretch" Width="Auto" >
<ListView.View>
<GridView>
<GridViewColumn Header="I2C Device" Width="Auto" DisplayMemberBinding="{Binding I2CDevName}" />
<GridViewColumn Header="I2C Device Address" Width="Auto" DisplayMemberBinding="{Binding I2CDeviceAddress}" />
</GridView>
</ListView.View>
</ListView>
Thus using SelectedItem.I2CDeviceAddress gives me the deviceaddress in my Textbox.
Now my view model has a property for the Button and the textbox and has the following method which gets invoked when button is clicked:
public void I2CReadCommandExecuted()
{
ReadMessage = string.Empty;
Byte[] buffer = new Byte[512];
int address;
string strValue = AddressMessage;
if (strValue.StartsWith("0x"))
{
strValue = strValue.Remove(0, 2);
address = Convert.ToInt32(strValue);
mComm.setAddress(address);
}
}
// This is for textBox
private string _AddressMessage = string.Empty;
public string AddressMessage
{
get
{
return _AddressMessage;
}
set
{
_AddressMessage = value;
NotifyPropertyChanged("AddressMessage");
}
}
// Property for ListView
public ObservableCollection<I2CModel> I2CDeviceList
{
get { return _I2CDeviceList; }
set
{
_I2CDeviceList = value;
NotifyPropertyChanged("I2CDeviceList");
}
}
// Property for Selected Item in ListView
private I2CModel _selectedI2CDeviceList;
public I2CModel SelectedI2CDeviceList
{
get { return _selectedI2CDeviceList; }
set
{
_selectedI2CDeviceList = value;
NotifyPropertyChanged("SelectedI2CDevSize");
}
}
Basically I have to remove the 0x from the value and store the hexadecimal value in my integer variable.
Here I am facing two issues:
When I put both Text="{Binding SelectedItem.I2CDeviceAddress, Path=AddressMessage, Mode=TwoWay, ElementName=I2cDeviceList}" the seelcted address from the listview doesnt appear in my textbox. The moment I remove Path=AddressMessage, Mode=TwoWay,, it works fine. How to make sure both of them work smoothly? Is their any other way I can get the selected item from the listview and display it in my textbox?
By using string strValue = AddressMessage; I am trying to save the content of AddressMessage in the string but when I debug my code, it always shows "null" even though I have "0x23"(hardcoded) in my textbox. Due to this I get the following error: Object reference not set to an instance of an object. at the beginning of if condition.
I tried my level best but it ain't happening. Am i missing something?
First of all there is no need to have seperate AddressMessage property. It can be done using SelectedI2CDeviceList. But still if you want to use it it can be achieved through below changes -
Set AddressMessage property when the selected item of listview changes
public I2CModel SelectedI2CDeviceList
{
get { return _selectedI2CDeviceList; }
set
{
_selectedI2CDeviceList = value;
AddressMessage = _selectedI2CDeviceList.I2CDeviceAddress;
NotifyPropertyChanged("SelectedI2CDevSize");
}
}
Also change the binding of textbox to below one:
<TextBox
Name="AddressI2C"
Text="{Binding Path=AddressMessage, Mode=TwoWay}" />
Hence whenever selected item of the listview changes it will set the content for textbox and when AddressMessage property is properly set you want get your second issue.
Hope this helps.
I have a ListView like:
Col1 Col2 Col3
1 A I
2 B II
3 C III
I use 2 buttons. When I click on the first button the Col3 should collapse and it should be visible when a click in the second button.
Any idea on how to do such a ListView in WPF?
Use of Thumb will solve the problem.
Just as
<ListView x:Name="MyListView"IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Items}", Mode=Default,
Source={StaticResource DataProvider}}"
Thumb.DragDelta="Thumb_DragDelta">
public Window1()
{
InitializeComponent();
MyListView.AddHandler(Thumb.DragDeltaEvent,
new DragDeltaEventHandler(Thumb_DragDelta),
true );
}
void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
{
Thumb senderAsThumb = e.OriginalSource as Thumb;
GridViewColumnHeader header = senderAsThumb.TemplatedParent as GridViewColumnHeader;
if (header.Column.ActualWidth < MIN_WIDTH)
{
header.Column.Width = MIN_WIDTH;
}
if (header.Column.ActualWidth > MAX_WIDTH)
{
header.Column.Width = MAX_WIDTH;
}
}
Could you provide some xaml-code of what your listview looks like?
You could bind a RelayCommand to your button and pass the ListView as a parameter. Then you could set Visibility = False.
<Button Command="{Binding MyButtonCommand}
CommandParameter="{Binding ElementName=Col3}" />
This would be your cs:
ICommand _myButtonCommand;
public ICommand MyButtonCommand
{
get
{
if (_myButtonCommand== null) _myButtonCommand= new RelayCommand(param => HideList(param ));
return _myButtonCommand;
}
}
void HideList(object param){
(param as ListView).Visibility = False;
}
I'm talking about RelayCommand as in Josh Smith's example: http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
you can dl the code there.
I guess you could achieve a similar result in xaml only with triggers, however I'm not as experienced with that subject.
I am using the code as
<Grid>
<ListView HorizontalContentAlignment="Stretch" Margin="38,12,31,110">
<ListView.View>
<GridView>
<GridViewColumn Header="COL1" Width="100"/>
<GridViewColumn Header="COL2" Width="100"/>
<GridViewColumn Header="COL3" Width="100"/>
</GridView>
</ListView.View>
</ListView>
<Button Height="25" HorizontalAlignment="Left" Margin="105,0,0,51"
Name="Collapse" VerticalAlignment="Bottom" Width="75"
Command="{Binding MyButtonCommand}"
CommandParameter="{Binding ElementName = COL3}">Collapse</Button>
<Button Height="25" HorizontalAlignment="Right" Margin="0,0,111,51" Name="Expand"
VerticalAlignment="Bottom" Width="75">Expand</Button>
</Grid>
and in CS
ICommand _myButtonCommand;
public ICommand MyButtonCommand
{
get
{
if (_myButtonCommand== null) _myButtonCommand = new RelayCommand(param => HideList(param ));
return _myButtonCommand;
}
}
void HideList( object param )
{
( param as ListView ).Visibility = Visibility.Hidden;
}
Can u give me a better idea?
I'd have put this answer as a comment of your post, but I'm not able to comment yet, so...
You have to name (use the "Name" Property) the element that you want to access via "Binding ElementName" or you won't be able to get it. In your case you should explicitly create the GridViewColumnHeader, because GridViewColumn has no Visibilty property:
<GridViewColumnHeader Name="COL3">COL3</GridViewColumnHeader>
You probably also have to explicitly create the content of your GridViewColumn if you want to make it disappear, though. This means you have to use GridViewColumn.DisplayMemberBinding or GridViewColumn.CellTemplate and give those a Name as well or access them via RelativeSource.
Have a look at this for the possibilities: http://www.wpfwiki.com/Default.aspx?Page=WPF%20Q5.3&AspxAutoDetectCookieSupport=1
However, have you thought about using an expander, yet?