i'm working on making a notepad++ equivalent in C# using MVVM design pattern for an university assignment. I've created the tabs successfully but now I have a problem adding the little "*" to the tabname when the content changes from the original and making it disappear upon saving. How can this be implemented ?
Here is the code for the tabcontrol:
<TabControl Margin="10,26,10,10" Grid.Column="2" ItemsSource="{Binding FileTabs}" SelectedIndex="{Binding CurrentSelectedTab}">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FileTabName}" />
<Button Command="{Binding Close,
RelativeSource={RelativeSource AncestorType={x:Type local:FileMenuCommands}},
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" Width="20" Height="20" Content="X"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBox Text="{Binding FileTabContent, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" AcceptsReturn="True" AcceptsTab="True" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
The model for the tabfile:
using System;
using System.Collections.Generic;
using System.Text;
namespace Notepad___.Model
{
class FileTabProvider
{
public string FileTabName { get; set; }
public string FileFullPath { get; set; }
public string FileTabContent { get; set; }
public FileTabProvider(string FileTabName, string FileFullPath, string FileTabContent)
{
this.FileTabName = FileTabName;
this.FileFullPath = FileFullPath;
this.FileTabContent = FileTabContent;
}
}
}
Also the two save functions created in the view model of the mainwindow:
private void SaveFile(object parameter)
{
if (FileTabs[CurrentSelectedTab].FileFullPath == "")
SaveAsFile(parameter);
else
File.WriteAllText(FileTabs[CurrentSelectedTab].FileFullPath, FileTabs[CurrentSelectedTab].FileTabContent.ToString());
}
private void SaveAsFile(object parameter)
{
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.Filter = "Text files (*.txt)|*.txt|All files (*.*)|*.*";
if (saveFileDialog.ShowDialog() == true)
File.WriteAllText(saveFileDialog.FileName, FileTabs[CurrentSelectedTab].FileTabContent.ToString());
}
Implement the INotifyPropertyChanged interface in your view model and change the FileTabName property whenever the FileTabContent property is set. Something like this:
class FileTabProvider : INotifyPropertyChanged
{
private string _originalFileTabName;
private string _fileTabName;
public string FileTabName
{
get { return _fileTabName; }
set { _fileTabName = value; OnPropertyChanged(nameof(FileTabName)); }
}
public string FileFullPath { get; set; }
private string _fileTabContent;
public string FileTabContent
{
get { return _fileTabContent; }
set
{
_fileTabContent = value;
FileTabName += "*";
}
}
public FileTabProvider(string fileTabName, string fileFullPath, string fileTabContent)
{
_fileTabName = fileTabName;
FileFullPath = fileFullPath;
_fileTabContent = fileTabContent;
}
public void Save() => FileTabName = _originalFileTabName;
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Whenever you save, you need to remember to reset the filename:
private void SaveFile(object parameter)
{
var tab = FileTabs[CurrentSelectedTab];
if (tab.FileFullPath == "")
SaveAsFile(parameter);
else
File.WriteAllText(tab.FileFullPath, tab.FileTabContent.ToString());
tab.Save();
}
You can do it this way:
<Grid>
<TabControl>
<TabItem Header="ABC" x:Name="TabItem1" KeyDown="TabItem1_OnKeyDown">
<TabItem.Content>
<Grid>
<TextBox Text="{Binding YourTextProp}" TextChanged="TextBoxBase_OnTextChanged"/>
</Grid>
</TabItem.Content>
</TabItem>
</TabControl>
</Grid>
public MainWindow()
{
InitializeComponent();
}
private void TextBoxBase_OnTextChanged(object sender, TextChangedEventArgs e)
{
if (TabItem1.Header is string tabItemHeader && !tabItemHeader.Contains("*"))
{
tabItemHeader += "*";
TabItem1.Header = tabItemHeader;
}
}
private void TabItem1_OnKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.S && Keyboard.Modifiers == ModifierKeys.Control && TabItem1.Header is string tabItemHeader)
{
tabItemHeader = tabItemHeader.Substring(0, tabItemHeader.Length - 1);
TabItem1.Header = tabItemHeader;
}
}
Related
Problem
I want to refresh my wpf view when a change is made in a List of objects in my application, but it wont register the INotifyChanged method when I change a value.
What I've tried
I went to multiple different stackoverflow pages with sort of the same problem but I don't get it working right. It wont register a change in a object in the list.
my code
below is the code for the MainWindow of the WPF application in wher with the last button click I change the value of XLocation in an object out of a list.
public partial class MainWindow : Window
{
private string filePathArtist { get; set; }
private string filePathGrid { get; set; }
public Game.Game Game { get; set; }
public MainWindow()
{
InitializeComponent();
filePathGrid = String.Empty;
filePathArtist = String.Empty;
}
private void BtnOpen_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
bool? res = openFileDialog.ShowDialog();
if (res == true)
{
string filepathgrid = openFileDialog.FileName;
filePathGrid = filepathgrid;
GridTextBox.Text = filepathgrid;
}
}
private void PickArtistBtn_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
bool? res = openFileDialog.ShowDialog();
if (res == true)
{
string filepathartist = openFileDialog.FileName;
filePathArtist = filepathartist;
ArtistTextBox.Text = filepathartist;
}
}
private void CreateGridBtn_Click(object sender, RoutedEventArgs e)
{
Game = new Game.Game(filePathGrid, filePathArtist);
this.DataContext = Game;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Game.Artists[0].XLocation = 30;
}
}
Next code is the Game class where is implemented a INotfyPropertyChanged on the list of Artists.
public class Game : INotifyPropertyChanged
{
public List<Artist> _artists;
public List<Artist> Artists
{
get
{
return _artists;
}
set
{
_artists = value;
OnPropertyChanged("Artists");
}
}
public List<ITile> Tiles { get; set; }
public Game()
{
}
public Game(string graphPath, string artistPath)
{
IDataParser graphParser = DataFactory.DataFactory.Instance.CreateParser(graphPath);
IDataParser artistParser = DataFactory.DataFactory.Instance.CreateParser(artistPath);
Tiles = graphParser.ParseGridData(graphPath);
Artists = artistParser.ParseArtistData(artistPath);
Test = "new Game";
}
public string Test { get; set; } = "t";
public event PropertyChangedEventHandler? PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Ive also added the INotifyPropertyChanged in the Artists class
public class Artist : INotifyPropertyChanged
{
private float _xLocation;
private float _yLocation;
private int _xVelocity;
private int _yVelocity;
public float XLocation
{
get => _xLocation;
set
{
_xLocation = value;
OnPropertyChanged("XLocation");
}
}
public float ConvertedXLoc
{
get => XLocation * (float)3.75;
set { }
}
public float YLocation
{
get => _yLocation;
set
{
_yLocation = value;
OnPropertyChanged("YLocation");
}
}
public float ConvertedYLoc
{
get => YLocation * (float)3.75;
set { }
}
public int XVelocity
{
get => _xVelocity;
set
{
_xVelocity = value;
}
}
public int YVelocity
{
get => _yVelocity;
set
{
_yVelocity = value;
}
}
public event PropertyChangedEventHandler? PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Then here is the Xaml code where I bind the objects to the wpf UI.
<Window x:Class="BroadwayBoogie.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:game="clr-namespace:BroadwayBoogie.Game"
mc:Ignorable="d"
Title="MainWindow" Height="900" Width="900"
>
<Window.DataContext>
<game:Game/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="201*"/>
<ColumnDefinition Width="199*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="21*"/>
<RowDefinition Height="401*"/>
<RowDefinition Height="20*"/>
</Grid.RowDefinitions>
<Button x:Name="BtnOpen" Content="Pick Grid" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Click="BtnOpen_Click"/>
<TextBox x:Name="GridTextBox" HorizontalAlignment="Center" TextWrapping="NoWrap" VerticalAlignment="Center" Width="266" />
<Button x:Name="PickArtistBtn" Content="Pick Artist" HorizontalAlignment="Left" Margin="356,0,0,0" VerticalAlignment="Center" Click="PickArtistBtn_Click" RenderTransformOrigin="-0.135,0.647"/>
<TextBox x:Name="ArtistTextBox" HorizontalAlignment="Left" Margin="30,14,0,0" TextWrapping="NoWrap" VerticalAlignment="Top" Width="231" Grid.Column="1"/>
<Button x:Name="CreateGridBtn" Grid.Column="1" Content="Create Grid" HorizontalAlignment="Left" Margin="311,14,0,0" VerticalAlignment="Top" Click="CreateGridBtn_Click"/>
<Canvas Width="800" Height="800" Grid.ColumnSpan="2" Margin="49,15,51,27" Grid.Row="1" Background="DarkSeaGreen" Grid.RowSpan="2">
<ItemsControl Name="tilesItemsControl" ItemsSource="{Binding Tiles}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<Rectangle
Width="15"
Height="15"
Fill="{Binding Color}"
Canvas.Left ="{Binding ConvertedXLoc}"
Canvas.Top="{Binding ConvertedYLoc}" />
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl Name="ArtistItemsControl" ItemsSource="{Binding Artists}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<Rectangle
Width="3.75"
Height="3.75"
Fill="Black"
Canvas.Left ="{Binding ConvertedXLoc}"
Canvas.Top="{Binding ConvertedYLoc}" />
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
<Grid/>
<Button Content="Button" HorizontalAlignment="Left" Margin="4,0,0,0" Grid.Row="2" VerticalAlignment="Center" Click="Button_Click"/>
</Grid>
So with the press of the button I added for testing purposes. It changes a value in the List and then the PropertyChanged method should detect that but it doesn't detect it it just skips it.
Question
So my basic question is, how do I detect the change of a property from objects out of the List of Artists.
OnPropertyChanged will only be executed when the property itself is changed. A new item in a list is not a property change. That's the reason why no updates happens.
Instead a List, try an ObservableCollection. An ObservableCollection implements an additional INotifyCollectionChanged which makes the UI able to react on changing items in the list.
I have a problem with the listviewItem, is that when you change the data if they do it but they are not saved in the interface when you click on another item
This problem happens when binding the textbox to the listviewItem
MainPage.xaml
<Grid RequestedTheme="Light">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="818*" />
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<TextBox
x:Name="titulo"
Grid.Row="0"
FontSize="40"
PlaceholderText="Ingresa tu titulo"
KeyDown="Titulo_KeyDown"
/>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<ListView
x:Name="listNotas"
Width="450"
Background="DimGray"
SelectionChanged="ListNotas_SelectionChanged">
<ListView.ItemTemplate>
<DataTemplate >
<StackPanel>
<TextBlock Text="{Binding title, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<RichEditBox
x:Name="editor"
Width="760"
HorizontalAlignment="Stretch" />
</StackPanel>
<GridView
Name="stpanel"
Grid.Row="2"
Height="50">
<TextBlock Text="" Name="Tester"/>
</GridView>
MainPage.xaml.cs
public string editpath = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "Notas.json" );
public ObservableCollection<Notes> Mynotes;
public MainPage()
{
this.InitializeComponent();
// Load data of Notas.json to Listview
LoadUpdate();
}
private void LoadUpdate()
{
using (StreamReader file = File.OpenText(editpath))
{
var json = file.ReadToEnd();
baseNotes mainnotes = JsonConvert.DeserializeObject<baseNotes>(json);
Mynotes = new ObservableCollection<Notes>();
foreach (var item in mainnotes.notes)
{
Mynotes.Add(new Notes { title = item.title });
}
listNotas.ItemsSource = null;
listNotas.ItemsSource = Mynotes;
listNotas.SelectedIndex = 0;
}
}
private void ListNotas_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
string json = File.ReadAllText(editpath);
dynamic jsonObj = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
titulo.Text = jsonObj["notes"][listNotas.SelectedIndex]["title"];
}
private void Titulo_KeyDown(object sender, KeyRoutedEventArgs e)
{
#region
string json = File.ReadAllText(editpath);
dynamic jsonObj = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
int indice = listNotas.SelectedIndex;
jsonObj["notes"][indice]["title"] = titulo.Text;
string output = Newtonsoft.Json.JsonConvert.SerializeObject(jsonObj);
File.WriteAllText(editpath, output);
// Show json file text in RicheditBox
editor.TextDocument.SetText(Windows.UI.Text.TextSetOptions.None, output);
//Problem
Binding myBinding = new Binding();
myBinding.Source = Mynotes[indice];
myBinding.Path = new PropertyPath("title");
myBinding.Mode = BindingMode.TwoWay;
myBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
BindingOperations.SetBinding(titulo, TextBox.TextProperty, myBinding);
#endregion
}
Model: Notes.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml.Controls;
namespace Realtimejsonedit
{
public class Notes : INotifyPropertyChanged
{
public int created { get; set; }
//public string title { get; set; }
private string Title;
public string title
{
get { return Title; }
set {
Title = value;
NotifyPropertyChanged("title");
}
}
public string text { get; set; }
public int id { get; set; }
public int updated { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class baseNotes
{
public List<Notes> notes { get; set; }
}
}
as I say the problem as I am doing the binding but when executing ListNotas.SelectionChanged the values that were saved in the json file are changed, but they do not remain in the listviewitem, although the binding is in the Keydown event and not in ListNotas. SelectionChanged.
the problem:
https://i.imgur.com/IGcd8iz.gif
What I want to achieve:
https://i.imgur.com/KnkbQw9.gif
UWP - How to save ListViewItem state if the data source has changed?
The problem is that you set bind repeatedly in Titulo_KeyDown event. For your requirement, you could bind ListView SelectItem once. For more please refer the following steps:
ViewModel
public class ViewModel : INotifyPropertyChanged
{
public string editpath = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "Notas.json");
public ObservableCollection<Notes> Mynotes { get; set; }
public ViewModel()
{
LoadUpdate();
SetSelectIndex(0);
}
private void SetSelectIndex(int index)
{
SelectItem = Mynotes[index];
}
private void LoadUpdate()
{
using (StreamReader file = File.OpenText(editpath))
{
var json = file.ReadToEnd();
baseNotes mainnotes = JsonConvert.DeserializeObject<baseNotes>(json);
Mynotes = new ObservableCollection<Notes>();
foreach (var item in mainnotes.notes)
{
Mynotes.Add(new Notes { title = item.title });
}
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private Notes _selectItem;
public event PropertyChangedEventHandler PropertyChanged;
public Notes SelectItem
{
get
{
return _selectItem;
}
set
{
_selectItem = value;
OnPropertyChanged();
}
}
}
Xaml
<Page.DataContext>
<local:ViewModel />
</Page.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="818*" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<TextBox
x:Name="titulo"
Grid.Row="0"
FontSize="40"
PlaceholderText="Ingresa tu titulo"
Text="{Binding SelectItem.title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextChanged="Titulo_TextChanged"
/>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<ListView
x:Name="listNotas"
Width="450"
Background="DimGray"
ItemsSource="{Binding Mynotes}"
SelectedItem="{Binding SelectItem, Mode=TwoWay}"
>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<RichEditBox
x:Name="editor"
Width="760"
HorizontalAlignment="Stretch"
/>
</StackPanel>
<GridView
Name="stpanel"
Grid.Row="2"
Height="50"
>
<TextBlock Name="Tester" Text="" />
</GridView>
</Grid>
Code behind (write the data to json)
public sealed partial class MainPage : Page
{
private dynamic jsonObj;
public string editpath = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "Notas.json");
public ObservableCollection<Notes> Mynotes;
public MainPage()
{
this.InitializeComponent();
string json = File.ReadAllText(editpath);
jsonObj = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
}
private void Titulo_TextChanged(object sender, TextChangedEventArgs e)
{
#region
int indice = listNotas.SelectedIndex;
jsonObj["notes"][indice]["title"] = titulo.Text;
string output = Newtonsoft.Json.JsonConvert.SerializeObject(jsonObj);
editor.TextDocument.SetText(Windows.UI.Text.TextSetOptions.None, output);
File.WriteAllText(editpath, output);
#endregion
}
}
This is sample project.
In the view, I have a ListView that should be shown a with a data binding with an Observable collection of string, but not shown anything
If instead of listview I put a label and the observable collection turns it into a simple string I see the data
In the Main view:
<ListView Grid.Row="1" Grid.Column="1" ItemsSource="{Binding SerialsPorts}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding SerialPortName}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In the Main ViewModel
class BrightnessSerialsPortsViewModel : INotifyPropertyChanged, IBrightnessSerialsPortsViewModel
{
//readonly IPageDialogService pageDialogService;
readonly IBrightnessSerialsPortsManager manager;
public BrightnessSerialsPortsViewModel()
{
//pageDialogService = new PageDialogService();
manager = new BrightnessSerialsPortsManager();
manager.BrightnessInfoUpdated += OnBrightnessInfoUpdated;
manager.DeviceSerialPortsInfoUpdated += OnDeviceSerialsPortsInfoUpdated;
PageAppearingCommand = new Command(OnPageAppearing);
PageDisappearingCommand = new Command(OnPageDisappearing);
}
void OnPageAppearing()
{
//pageDialogService.DisplayAlert("Invoke Command Demo Page", "Appearing event fired.", "OK");
manager.GetBrightness();
manager.GetSerialsPorts();
}
void OnPageDisappearing()
{
//pageDialogService.DisplayAlert("Invoke Command Demo Page", "Disappearing event fired.", "OK");
SerialTest = "";
}
private void OnDeviceSerialsPortsInfoUpdated(object sender, IDeviceSerialsPortsInfoEventArgs e)
{
foreach(string device in e.DeviceSerialsPorts.Devices)
{
ISerialsPortsViewModel serialsPortsViewModel = new SerialsPortsViewModel(device);
SerialsPorts.Add(serialsPortsViewModel);
SerialTest += device + Environment.NewLine;
}
}
private void OnBrightnessInfoUpdated(object sender, IBrightnessInfoEventArgs e)
{
float f = e.DeviceBrightness.Brightness;
decimal dec = new decimal(f);
Brightness = (double) dec;
}
//public ICommand ChangeBrightnessCommand { get; set; }
public ICommand PageAppearingCommand { get; private set; }
public ICommand PageDisappearingCommand { get; private set; }
public ICommand ChangeBrightnessCommand => new RelayCommand(() => ExcecuteChangeBrightnessCommand());
public void ExcecuteChangeBrightnessCommand()
{
}
private ObservableCollection<ISerialsPortsViewModel> serialsPorts = new ObservableCollection<ISerialsPortsViewModel>();
public ObservableCollection<ISerialsPortsViewModel> SerialsPorts { get=> serialsPorts ; set { serialsPorts = value; OnPropertyChanged(nameof(SerialsPorts)); } }
private string serialstest = "";
public string SerialTest { get => serialstest; set {serialstest = value ; OnPropertyChanged(nameof(SerialTest)); } }
private double brightness = 1.0;
public double Brightness { get => brightness; set {brightness = value ; OnPropertyChanged(nameof(Brightness)); } }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
In the SerialPorts ViewModel:
public SerialsPortsViewModel(string serialPortName)
{
SerialPortName = serialPortName;
}
private string serialPortName;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string SerialPortName { get=> serialPortName; set {serialPortName = value ; OnPropertyChanged(nameof(SerialPortName)); } }
What am I doing wrong?
Solved change the View to this:
<ListView Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" ItemsSource="{Binding SerialsPorts}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell Height="60">
<StackLayout Orientation="Horizontal">
<BoxView BackgroundColor="Blue" WidthRequest="10" Margin="0,0,0,10" />
<StackLayout BackgroundColor="White" Orientation="Vertical" Margin="5,5,10,5">
<Label Text="{Binding SerialPortName}" FontAttributes="Bold" />
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have a custom control ChatTextControl with 2 textbox and a button.
The xaml is like this :
<Grid Background="White">
<Grid.ColumnDefinitions>
<!-- Message -->
<ColumnDefinition Width="*"/>
<!-- delete message -->
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Message content -->
<StackPanel>
<TextBlock Text="{Binding Path= Pseudo}" FontWeight="SemiBold"/>
<TextBlock Text="{Binding Path= Message}" TextWrapping="Wrap"/>
</StackPanel>
<Button Grid.Column="1" Padding="8" VerticalAlignment="Top" Width="20" Height="20" Background="{x:Null}" Click="Button_Click"/>
</Grid>
The pseudo and message come from the following class :
public class ChatListItemViewModel : BaseViewModel
{
public string Pseudo { get; set; }
public string Message { get; set; }
public int Id { get; set; }
}
ChatTextControl is called in another custom control ChatListControl:
<Grid Background="White">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:ChatTextControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
In my main window I call ChatListControl like so :
<local:ChatListControl x:Name="MyChat" Margin="389,10,10,38"/>
And set the DataContext in code behind :
ChatListModel chat = new ChatListModel();
MyChat.DataContext = chat;
ChatListModel :
public class ChatListModel : ChatListViewModel
{
private static int idCount = 0;
public ChatListModel()
{
Items = new List<ChatListItemViewModel>();
}
public void AddMessage(string p, string m)
{
Items.Add(new ChatListItemViewModel
{
Pseudo = p,
Message = m,
Id = idCount
});
idCount++;
}
}
The goal is to use the Button_Click event in ChatTextControl to delete the element with the corresponding id in the list.
But i don't know how to get the id in the code behind whether it's in the ChatTextControl.cs or MainWindow.cs.
If someone know how to do it or have a better idea for the delete button please let me know.
I could not verify the answer of mm8 because of the reason put in my comment, so here is the solution that I found.
After putting break point in the Button_Click event, I noticed that I could obtain the Id of ChatListItemViewModel by casting the this.DataContext in ChatTextControl and send an event like this :
public delegate void DeleteClick(int id);
public static event DeleteClick OnDeleteClick;
private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
{
OnDeleteClick?.Invoke(((ChatListItemViewModel)this.DataContext).Id);
}
Doing so, I can get the Id and delete the item in the main window :
public ChatListModel chat;
public MainWindow()
{
InitializeComponent();
chat = new ChatListModel();
chat.AddMessage(name, "Hello World!");
MyChat.DataContext = chat;
ChatTextControl.OnDeleteClick += ChatTextControl_OnDeleteClick;
}
private void ChatTextControl_OnDeleteClick(int id)
{
chat.DelMessage(id);
MyChat.DataContext = null;
MyChat.DataContext = chat;
}
You could for example set an IsDelete property in ChatListItemViewModel when the button is clicked, raise an event and handle this event in ChatListModel. You need to use an ObservableCollecton<T> instead of a List<T> for the item to get removed in the view:
public class ChatListModel : ChatListViewModel
{
private static int idCount = 0;
public ChatListModel()
{
Items = new ObservableCollection<ChatListItemViewModel>();
AddMessage("p", "m");
}
public void AddMessage(string p, string m)
{
ChatListItemViewModel newItem = new ChatListItemViewModel
{
Pseudo = p,
Message = m,
Id = idCount
};
newItem.PropertyChanged += NewItem_PropertyChanged;
Items.Add(newItem);
idCount++;
}
private void NewItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
ChatListItemViewModel removedItem = (ChatListItemViewModel)sender;
removedItem.PropertyChanged -= NewItem_PropertyChanged;
Items.Remove(removedItem);
idCount--;
}
public ObservableCollection<ChatListItemViewModel> Items { get; }
}
public class ChatListItemViewModel : BaseViewModel
{
public string Pseudo { get; set; }
public string Message { get; set; }
public int Id { get; set; }
private bool _isDeleted;
public bool IsDeleted
{
get { return _isDeleted; }
set { _isDeleted = value; OnPropertyChanged(nameof(IsDeleted)); }
}
public ChatListItemViewModel()
{
DeleteCommand = new RelayCommand(_ => true, _ => IsDeleted = true);
}
public ICommand DeleteCommand { get; }
}
ChatTextControl.xaml:
<Button Grid.Column="1" Padding="8" VerticalAlignment="Top" Width="20" Height="20"
Background="{x:Null}" Command="{Binding DeleteCommand}" />
I am trying to use Prism in a C# to but I seem to have set it up so that it binds to items in my model and not my viewmodel. It is a short program that is more of a learning tool that anything. When I move the items to the Viewmodel the SetProperty's don't seem to notify the view of the change.
Any thoughts as to how I have this setup backwards?
Model:
namespace XMLValueModifier_Threaded_WPF.Models
{
public class XMLReadFileModel : BindableBase
{
private string _XMLMasterFile2 = "0";
public string XMLGetFileName()
{
if (_XMLMasterFile2 != "1")
{
Microsoft.Win32.OpenFileDialog _XMLMasterFileDialog = new Microsoft.Win32.OpenFileDialog();
_XMLMasterFileDialog.DefaultExt = "xml";
_XMLMasterFileDialog.Filter = "xml Files (*.xml; *.XML) | *.xml; *.XML";
Nullable<bool> result = _XMLMasterFileDialog.ShowDialog();
if (result == true)
{
return _XMLMasterFileDialog.FileName;
}
return "";
}
else
{
return "";
}
}
}
}
ViewModel:
namespace XMLValueModifier_Threaded_WPF.ViewModels
{
public class MainDialogueViewModel : BindableBase
{
private string _XMLMasterFile;
public ICommand MasterFileLocation
{
get;
set;
}
public ICommand XMLFileLocation
{
get;
set;
}
public string XMLMasterFile
{
get
{
return _XMLMasterFile;
}
set
{
SetProperty(ref _XMLMasterFile, value);
}
}
private XMLReadFileModel xmlReadFileModel = new XMLReadFileModel();
public MainDialogueViewModel()
{
XMLReadFileModel xmlReadFileModel = new XMLReadFileModel();
Message = "example message";
XMLMasterFile = "example File";
this.MasterFileLocation = new DelegateCommand(chooseFile, canChooseFile);
this.XMLFileLocation = new DelegateCommand(chooseFile, canChooseFile);
}
public void masterfilelocation()
{
MessageBox.Show("i am here");
return;
}
private void chooseFile()
{
XMLMasterFile = xmlReadFileModel.XMLGetFileName();
}
private bool canChooseFile()
{
if (XMLMasterFile != null)
{
return true;
}
else
{
return true;
}
}
}
}
XAML:
<Window x:Class="XMLValueModifier_Threaded_WPF.Views.MainDialogue"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:XMLValueModifier_Threaded_WPF.ViewModels" Width="625" Height="452"
>
<Grid Margin="0,-24,0,-3">
<TextBox x:Name="Textbox1" Text="{Binding MainDialogueViewModel.XMLMasterFile,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Height="23" Margin="25,120,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="425"/>
<TextBox x:Name="Textbox2" Text="{Binding MainDialogueViewModel.XMLFiles,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Height="23" Margin="25,188,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="425" RenderTransformOrigin="0.506,1.565"/>
<Label Content="Location of Master XML File" HorizontalAlignment="Left" Margin="25,89,0,0" VerticalAlignment="Top"/>
<Label Content="Location of XML File(s)" HorizontalAlignment="Left" Margin="25,157,0,0" VerticalAlignment="Top"/></GRID>
Assuming you have your DataContext setup correctly to an instance of MainDialogueViewModel; you don't need to include MainDialogueViewModel in your binding. Simply bind to the property name XMLMasterFile. Also keep in mind that if the value isn't different, then nothing will be updated.