I am trying to work out data binding in C# in a wpf application and I got struck.
I have a program and I need to bind my project settings, which can be changed and are displayed in a UI.
UI:
<ListView Name="lsview">
<ListView.DataContext >
<Binding Source="projpro"/>
</ListView.DataContext>
<ListView.View>
<GridView>
<GridViewColumn>
<TextBox Width="150" Name="projid" Text="{Binding Path=pp.test}" />
</GridViewColumn>
<GridViewColumn>
<TextBox Width="150">wtf from xaml</TextBox>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Code:
public MainWindow()
{
InitializeComponent();
displayImage(true);
projectProperties();
projpro pp = new projpro();
lsview.DataContext = pp;
}
class:
class projpro
{
string a="check1";
public string test
{
get { return a ; }
set
{
a = "check2";
}
}
}
The class projpro needs to be a list of several parameters. I want to know how to display it. I have set path to pp. and I want to display the value of test in the UI. I read some artcles on MSDN but still I am clear how to access the code from xaml.
The values are dynamic in the class.
please throw some light.
Thanks a lot in advance.
cheers
Try with the propertyName on the Cell Binding.
<TextBox Width="150" Name="projid" Text="{Binding Path=test}" />
Related
So I have a customised GridView with a data template that contains a TextBox and is populated by a list of a custom class called Player. I need to be able to retrieve both the instance of Player and the text in the TextBox and save them to a new custom class called Score.
<GridView x:Name="gridScore" ItemsSource="{x:Bind PlayerList}" IsItemClickEnabled="True">
<GridView.ItemTemplate>
<DataTemplate x:DataType="data:Player">
<StackPanel Orientation="Horizontal">
<TextBox x:Name="txtbxGridScore" TextChanged="txtbxGridScoreChangedEventHandler" />
<Image Source="{x:Bind ProfilePicture}"/>
<StackPanel VerticalAlignment="Center">
<TextBlock Text="{x:Bind FullName}" />
<TextBlock Text="{x:Bind Alias}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
<Button x:Name="buttonSave" Content="Save Scores" Style="{StaticResource BarButtonStyle}" Click="buttonSave_Click"/>
I come from a web-based Java background so this is a little bit new to me but it seems like it should be a fairly simple exercise.
Initially, I tried iterating through the GridView upon a Button Click and grabbing each Player item along with the TextBox Text and saving them to a List<> of Score, however, getting the TextBox value proved troublesome.
I then tried initialising a page scope List<> of Score and simply updating it each time the TextBox value was changed, however, I wasn't able to make this work either.
A solution for either approach will work fine for my purposes. Any input is appreciated!
If I correctly understood you this is one of the way to resolve your problem.
So let's assume that your model class Player have this structure:
public class Player {
public int PlayerID { get; set; }
public string ProfilePicture { get; set; }
public string FullName { get; set; }
public string Alias { get; set; }
public float PlayerScore { get; set; } // To store textbox value
}
So you can resolve this by using two way binding.
XAML part will look something like this:
<GridView x:Name="gridScore"
ItemsSource="{x:Bind PlayerList}"
IsItemClickEnabled="True">
<GridView.ItemTemplate>
<DataTemplate x:DataType="data:Player">
<StackPanel Orientation="Horizontal">
<TextBox x:Name="txtbxGridScore"
Text="{x:Bind PlayerScore, Mode=TwoWay}" />
<Image Source="{x:Bind ProfilePicture}" />
<StackPanel VerticalAlignment="Center">
<TextBlock Text="{x:Bind FullName}" />
<TextBlock Text="{x:Bind Alias}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
<Button x:Name="buttonSave"
Content="Save Scores"
Click="buttonSave_Click" />
I have initialized your PlayerList with some dummy data like this:
PlayerList = new ObservableCollection<Player>() {
new Player() {
FullName = "Player A", Alias = "AAA"
},
new Player () {
FullName = "Player B", Alias = "BBB"
}
};
As you can see in XAML I am binding your text box with PlayerScore property of Player model.
When I run this App I get screen like this:
I will input some data into TextBox and click Save button:
When I click on Save it will trigger the event that you wrote in Button part
In that event I have one foreach loop that will iterate through the list and one breakpoint and as you can see on first item "Player A" the PlayerScore value is 10:
Now you can find your players with some ID property or with some other way that you want. This is the most simple way to accomplish what you want.
Remark: This could be solved in a better way using MVVM pattern and other stuff but as you mentioned you are beginner so maybe it is better for you to solve it like this and after that go with more advanced technique. Hope that this was helpful for you.
I am building simple app where It show all the files of certain format in one listview. I have divided program to class "DataFiles" where I establish FileSystemWatcher if something change there. If something change then I want to update my Listview which is placed in MainWindows.xaml.
So I have my MainWindowViewModel.cs and DataFiles.cs and ListView in MainWindow.xaml. How to update List from DataFiles to MainWindow.xaml?
My DataFiles class if something helps:
public FileSystemWatcher filewatcher;
public string ConfigurationFilesSourcePath;
public ObservableCollection<Files> fileslist { get; protected set; } = new ObservableCollection<Files>();
public void InitializeFiles()
{
// Create a new FileSystemWatcher
filewatcher = new FileSystemWatcher();
// Set filter
filewatcher.Filter = "*.txt";
// Set the path
filewatcher.Path = ConfigurationFilesSourcePath;
// Subscribe to the Created event
filewatcher.Created += new FileSystemEventHandler(FileOnchanged);
filewatcher.Changed += new FileSystemEventHandler(FileOnchanged);
filewatcher.Deleted += new FileSystemEventHandler(FileOnchanged);
filewatcher.Renamed += new RenamedEventHandler(FileOnRenamed);
// Enable the FileSystemWatcher events
filewatcher.EnableRaisingEvents = true;
RefreshFilesList();
}
private void FileOnchanged(object sender, FileSystemEventArgs e)
{
RefreshFilesList();
}
private void FileOnRenamed(object sender, RenamedEventArgs e)
{
RefreshFilesList();
}
public void RefreshFilesList()
{
fileslist.Clear();
//string[] getfiles = Directory.GetFiles(FolderLocation);
DirectoryInfo dir = new DirectoryInfo(ConfigurationFilesSourcePath);
string[] extensions = new[] { ".txt" };
int nof = 0;
foreach (FileInfo file in dir.GetFiles().Where(f => extensions.Contains(f.Extension.ToLower())).ToArray())
{
nof++;
fileslist.Add(new Files()
{
FileId = nof,
FileName = file.Name,
FileChanged = file.LastWriteTime.ToString(),
FileCreated = file.CreationTime.ToString(),
OnlyNameWithoutExtension = Path.GetFileNameWithoutExtension(file.Name)
});
}
NotifyPropertyChanged("fileslist");
}
If you think that I must be pretty new in WPF, You're right :) So sorry if this is stupid question. Please for help.
XAML:
<ListView Name="lvfiles" Grid.Row="4" ItemsSource="{Binding fileslist}" SelectionMode="Single" SelectedItem="{Binding SelectedFiles}" DataContext="{Binding }" Style="{StaticResource ListView}">
<ListView.View>
<GridView x:Name="gridFiles">
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox Tag="{Binding ID}" IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListViewItem}}, Path=IsSelected}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn x:Name="FileId" Header="#" DisplayMemberBinding="{Binding FileId}" Width="Auto"/>
<GridViewColumn x:Name="FileName" Header="{inf:Loc ConfigurationsName}" Width="Auto">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding FileName, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" BorderThickness="0" Style="{StaticResource ListViewTextBoxes}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<!--<GridViewColumn x:Name="FileName" Header="{inf:Loc ConfigurationsName}" DisplayMemberBinding="{Binding FileName}" Width="Auto"/>-->
<GridViewColumn x:Name="FileCreated" Header="{inf:Loc ConfigurationsCreated}" DisplayMemberBinding="{Binding FileCreated}" Width="Auto"/>
<GridViewColumn x:Name="FileChanged" Header="{inf:Loc ConfigurationsChanged}" DisplayMemberBinding="{Binding FileChanged}" Width="Auto"/>
</GridView>
</ListView.View>
</ListView>
In the mainmenu you should assign a handler to PropertyChanged event and inside it you can update the list.
DataDiles.PropertyChanged+= DataFiles_PropertyChanged;
public void DataFiles_PropertyChanged(/*...*/)
{
//Refresh the list
}
For more exact code please edit your question and add the definition of your DataFiles class completely.
I hope it will help.
The binding of ItemsSource to the ObservableCollection automatically synchronizes the listview with the collection for every collection.Add/Remove/Clear method call. So I wouldn't recommend to fully remake the collection for every filewatcher event. (If you had 99 files and 1 file was added, you would get 1 collection changed event for the Clear call and 100 events for the 100 Add calls.) Instead you could try to transfer the changes described by the filewatcher events exactly to collection changes: filewatcher.Created --> collection.Add; filewatcher.Deleted --> collection.Remove; filewatcher.Renamed --> collection.Remove + collection.Add. But if the filewatcher misses a file being created/deleted your list would not realize the change. (In fact I wouldn't trust the filewatcher that far.)
Or (easier) you don't use ObservableCollection but just ArrayList as type of your FilesList property. Than no event is raised during the Clear and Add calls and you trigger the synchronization of the ListView by calling NotifyPropertyChanged("fileslist") once at the end of RefreshFilesList as you already do.
Be sure that the DataFiles class implements INotifyPropertyChanged and that the DataContext of your ListView is the DataFiles instance (ideally located as property in your view model).
In my listview, I've three columns, the first column is displayed as text with image and the rest of the columns just text only. The listview is coded as below:
<TabItem x:Name="HistoryTab" Header="History" Style="{StaticResource TabStyle}">
<Grid>
<ListView x:Name="HistoryTabLv" HorizontalAlignment="Left" Height="164" Width="275" VerticalAlignment="Top" SelectionChanged="HistoryTabLv_SelectionChanged" SelectionMode="Single">
<ListView.View>
<GridView>
<GridViewColumn x:Name="TimeColumn" Header="Time" Width="85">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="-5,0,0,0">
<Image x:Name="Img" Height="12" Width="12" Source="{Binding Image}" Stretch="Uniform"/>
<TextBlock Text="{Binding Time}"/>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn x:Name="PhoneNumColumn" Header="Phone Number" Width="85" DisplayMemberBinding="{Binding PhoneNum}" />
<GridViewColumn x:Name="DirectionColumn" Header="Direction" Width="95" DisplayMemberBinding="{Binding Direction}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</TabItem>
If the action statement is true, the relevant data will be binded to each column as coded below.
private void HistoryTabLv_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (myStatement == true)
{
var uri = new Uri(#"/Resources/time.png", UriKind.Relative);
myImg = new BitmapImage(uri);
DateTime myTime = DateTime.Now;
HistoryTabLv.Items.Insert(0, new { Image = myImg, Time = myTime.ToString("hh:mm:ss tt"), PhoneNum = calledNum,
Direction = "Called out" });
}
}
In winform, if I want to get the second column value of the selected row, it is coded like this: (based on what I've searched)
string secondCol = lv.SelectedItems[0].SubItems[1].Text;
I want to get the second column value of the selected row (in my case is the PhoneNum column), how can I do that in WPF. I tried with the code below but it doesn't work. Please help.
string myText = (string)((DataRowView)HistoryTabLv.SelectedItems[0])["PhoneNum"];
In WPF ListViewItem is just a wrapper for your content object and SelectedItem(s) will be of the same type as item in your source collection so normally you would cast HistoryTabLv.SelectedItem to that type but because, as far as I can see, you use anonymous type it makes it a bit more difficult. I think the easiest way is around your problem is to use dynamic
dynamic selectedItem = HistoryTabLv.SelectedItem;
var phoneNum = selectedItem.PhoneNum;
or
dynamic selectedItem = HistoryTabLv.SelectedItems[0];
var phoneNum = selectedItem.PhoneNum;
EDIT
If you would create class for you item like
public class MyItemClass {
public string Image { get; set; }
public string Time { get; set; }
public string PhoneNum { get; set; }
public string Direction { get; set; }
}
and create your item like
new MyItemClass {
Image = myImg,
Time = myTime.ToString("hh:mm:ss tt"),
PhoneNum = calledNum,
Direction = "Called out"
}
then you could cast SelectedItem(s) to your item class like
var selectedItem = (MyItemType)HistoryTabLv.SelectedItem
I mean how should I display my datastructure for I can work with it like with table?
I want to have a table with rows and columns can be dynamicaly added and removed, but in the rest is should looks like a table.
Now it's represented like IList>, because I should resize it, as I said before. But now i want to display it in DataGrid, and be able to have rows, coulmns, and work with "Cells". But i cannot bind it properly, it it does not display anything, only an empty cell for every row.
What should I do? Or mby use arrays and resize them after each row/column addition?
Please, advice.
Now I have this one:
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
var storage = new Storage();
storage.Data.Add(new DataRow("Bla-bla")
{
new DataEntity() {Text = "bla-bla", Forces = new[] {ForceEnum.AA}},
new DataEntity() {Text = "bla-bla", Forces = new[] {ForceEnum.UA}}
});
DataListView.DataContext = new StorageModel(storage);
}
public class StorageModel
{
public StorageModel()
{
}
public StorageModel(IStorage storage)
{
DataRowList = new ObservableCollection<DataRow>(storage.Data);
}
public ObservableCollection<DataRow> DataRowList
{
get;
set;
}
}
public class DataRow : IList<DataEntity>
{
public string Name { get; private set; }
private readonly List<DataEntity> _list = new List<DataEntity>();
...
_
<ListView ItemsSource="{Binding DataRowList}" Name="DataListView">
<ListView.ItemTemplate>
<DataTemplate>
<ListView ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
i want be able to create something similar to this, but with 2-way binding...
You seem to be asking for TreeView and not ListView since you have a tree alike data structure.
If you do not want to use a TreeView I would suggest you to use a simple ListView with a small trick. That is you could explose an Expander which will add or remove items underneath parent with an indent to fake tree look and still all cells in column would be resized together.
Btw dont forgot to define columns
Here is how:
<ListView Margin="10" Name="lvUsers">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Age" Width="50" DisplayMemberBinding="{Binding Age}" />
<GridViewColumn Header="Mail" Width="150" DisplayMemberBinding="{Binding Mail}" />
</GridView>
</ListView.View>
</ListView>
If you wish to implement that expander trick behavior I would suggest you to listen to event OnExpanded and add or remove the needed rows.
I have a .xaml file which has a listview. Listview has 2 items inside which are bind in a following way:
<ListView Name="listView" ItemsSource="{Binding DeviceList}" SelectedItem="{Binding ConnectedDevice, Mode=TwoWay}" >
<ListView.View>
<GridView>
<GridViewColumn Width="300" Header="Name" DisplayMemberBinding="{Binding Description}" />
<GridViewColumn Width="240" Header="Connection Status" DisplayMemberBinding="{Binding DeviceName}" />
</GridView>
</ListView.View>
</ListView>
Both Description and Devicename are part of ModelClass.In My ViewModel class I am able to extract the Device name as well as Description from the hardware I have connected.
public ObservableCollection<ComDeviceInfo> DeviceList
{
get { return comDevices; }
set
{
comDevices = value;
NotifyPropertyChanged("DeviceList");
}
}
public ComDeviceInfo ConnectedDevice
{
get { return connectedDevice; }
set
{
connectedDevice = value;
NotifyPropertyChanged("ConnectedDevice");
}
}
//Called Inside Constructor
private void checkForDevicesTimer_Elapsed(Object source, ElapsedEventArgs e)
{
DeviceList = ComDeviceManagement.FindDevices();
}
Here ComDeviceManagement is my class which has FINDDevices() which returns me the devicename and description. U can notice DeviceList = ComDeviceManagement.FindDevices() above which indicates both the descrip and name are present inside the list.
Everything is working fine. But What I basically want is to display both the Devicename and Description in One Column rather than two separate columns. Well the problem I am facing here is with Devicename and Description. Even though they both display different values, Isn't their a way where I can concatinate them and display both the values into a single Column??? You may notice another column in .xaml file but I want to display(concatenate) both these inside my single column in listView.
How can i do that?
Please help!!
Use a MultiBinding with a format string.
<GridViewColumn>
<TextBlock>
<!-- the Text property is of type System.String -->
<TextBlock.Text>
<MultiBinding StringFormat="{0} {1}">
<Binding Path="Description "/>
<Binding Path="Name"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</GridViewColumn>
The thing you have to understand with a MultiBinding is that if the target property is not a string then you must provide a converter. If it is a string, you can get away with just using the format string.
So, in your case, you can't use it (easily) via the DisplayMemberBinding, you have to specify the content as in my example above.
Two approaches
In the ComDeviceInfo class just add a property that concatenates
public string DescName
{
get
{
return Description + " " + Name;
}
{
<GridViewColumn Width="300" Header="Name" DisplayMemberBinding="{Binding DescName}" />
Or use a multi-value converter
MultiBinding.Converter
Will provided and example.
I would add a new property, but don't forget to update it too when its components change.
private ComDeviceInfo _model;
public string DeviceName
{
get { return _model.Name; }
set
{
_model.Name = value;
NotifyPropertyChanged("DeviceName");
NotifyPropertyChanged("Combined");
}
}
public string Description
{
get { return _model.Description; }
set
{
_model.Description = value;
NotifyPropertyChanged("Description");
NotifyPropertyChanged("Combined");
}
}
public string Combined
{
get { return string.Format("{0} : {1}", _description, _deviceName; }
}
If you are using LINQ query to get your data you can do this:
var query = from devices in DeviceList
select new
{.DeviceDesc = devices.device + " " devices.desc}
listview.ItemsSource = query;
Then go into the GridView.Column and use:
DisplayMemberBinding="{Binding DeviceDesc}"
and it will bind the concatenated column you just created. Obviously you will need to write the correct query, mine was just an example to go by...