This commit is contained in:
Jon Lipsky 2019-06-23 15:10:43 -05:00
Родитель 50fb853d24 c7058214aa
Коммит f98c912170
11 изменённых файлов: 6532 добавлений и 6436 удалений

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -35,18 +35,18 @@ namespace HotUI.Forms.Sample {
protected override View Build () =>
new ScrollView{new Stack {
(state.CanEdit ?
(View)new Entry {
Text = state.Text,
(View)new Entry(state.Text){
Completed =(e)=> state.Text = e
}
: new Label { TextBinding = () => $"{state.Text}: multiText" }),// Fromated Text will warn you. This should be done by TextBinding
new Label {Text = state.Text},
new Button{Text = "Toggle Entry/Label", OnClick = ()=> state.CanEdit = !state.CanEdit},
new Button{Text = "Update Text", OnClick = ()=>{
: new Label (() => $"{state.Text}: multiText" )),// Fromated Text will warn you. This should be done by TextBinding
new Label (state.Text),
new Button("Toggle Entry/Label"){ OnClick = ()=> state.CanEdit = !state.CanEdit},
new Button("Update Text"){ OnClick = ()=>{
state.Text = $"Click Count: {clickCount.Value++}";
}
}
} };
}
};
}

Просмотреть файл

@ -29,21 +29,18 @@ namespace HotUI.Samples {
}
protected override View Build () =>
//new ScrollView{
new ScrollView{
new Stack {
(state.CanEdit ?
(View)new Entry {
Text = state.Text,
Completed =(e)=> state.Text = e
}
: new Label { TextBinding = () => $"{state.Text}: multiText" }),// Fromated Text will warn you. This should be done by TextBinding
new Label {Text = state.Text},
new Button{Text = "Toggle Entry/Label", OnClick = ()=> state.CanEdit = !state.CanEdit},
new Button{Text = "Update Text", OnClick = ()=>{
(state.CanEdit ? (View)new Entry(state.Text) {
Completed =(e)=> state.Text = e
} : new Label (() => $"{state.Text}: multiText" )),// Fromated Text will warn you. This should be done by TextBinding
new Label (state.Text),
new Button("Toggle Entry/Label"){ OnClick = ()=> state.CanEdit = !state.CanEdit},
new Button("Update Text"){ OnClick = ()=>{
state.Text = $"Click Count: {clickCount.Value++}";
}
}
//}
};
}
};
}
}

Просмотреть файл

@ -1,73 +1,212 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace HotUI {
public interface INotifyPropertyRead : INotifyPropertyChanged {
event PropertyChangedEventHandler PropertyRead;
}
public class BindingObject : INotifyPropertyRead {
public class BindingObject : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyRead;
public event PropertyChangedEventHandler PropertyChanged;
internal Action StateChanged;
internal protected Dictionary<string, object> dictionary = new Dictionary<string, object> ();
internal protected Action<BindingObject, List<(string property, object value)>> UpdateParentValueChanged;
internal protected string ParentProperty { get; set; }
BindingState bindingState = new BindingState ();
public BindingState BindingState {
get => bindingState;
set {
if (bindingState == value)
return;
bindingState = value;
foreach (var child in bindableChildren) {
child.BindingState = value;
}
}
protected T GetProperty<T> ([CallerMemberName] string propertyName = "")
{
PropertyRead?.Invoke (this, new PropertyChangedEventArgs (propertyName));
if (dictionary.TryGetValue (propertyName, out var val))
return (T)val;
return default;
}
internal object GetValue (string propertyName)
{
dictionary.TryGetValue (propertyName, out var val);
return val;
}
/// <summary>
/// Returns true if the value changed
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="currentValue"></param>
/// <param name="newValue"></param>
/// <param name="propertyName"></param>
/// <returns></returns>
protected bool SetProperty<T> (T value, [CallerMemberName] string propertyName = "")
{
if (dictionary.TryGetValue (propertyName, out object val)) {
if (EqualityComparer<T>.Default.Equals ((T)val, value))
return false;
}
dictionary [propertyName] = value;
OnPropertyChanged?.Invoke ((this, propertyName, value));
PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (propertyName));
return true;
}
internal Action<(object Sender, string PropertyName, object Value)> OnPropertyChanged { get; set; }
}
public class BindingObjectManager {
public BindingState BindingState { get; set; } = new BindingState ();
internal Action StateChanged;
bool isBuilding;
public bool IsBuilding => isBuilding;
public IEnumerable<KeyValuePair<string, object>> ChangedProperties => changeDictionary;
Dictionary<string, object> changeDictionary = new Dictionary<string, object> ();
List<(string property, object value)> pendingUpdates = new List<(string, object)> ();
List<string> listProperties = new List<string> ();
internal void ResetChangeDictionary ()
{
changeDictionary.Clear ();
}
Dictionary<string, object> changeDictionary = new Dictionary<string, object> ();
internal protected Dictionary<string, object> dictionary = new Dictionary<string, object> ();
List<(string property, object value)> pendingUpdates = new List<(string, object)> ();
bool isBuilding;
public bool IsBuilding => isBuilding;
internal void StartBuildingView ()
{
CheckForStateAttributes ();
CheckForStateAttributes (this);
isBuilding = true;
bindableChildren.ForEach (x => x.StartBuildingView ());
if (listProperties.Any ()) {
BindingState.AddGlobalProperties (listProperties);
}
listProperties.Clear ();
}
bool hasChecked = false;
static Assembly HotUIAssembly = typeof (BindingObject).Assembly;
void CheckForStateAttributes ()
List<INotifyPropertyRead> children = new List<INotifyPropertyRead> ();
Dictionary<object, string> childrenProperty = new Dictionary<object, string> ();
public void StartMonitoring (INotifyPropertyRead obj)
{
if (hasChecked)
if (children.Contains (obj))
return;
var type = this.GetType ();
children.Add (obj);
//Check in for more properties!
CheckForStateAttributes (obj);
if (obj is BindingObject bobj) {
bobj.OnPropertyChanged = (s) => OnPropertyChanged (s.Sender, s.PropertyName, s.Value);
} else {
obj.PropertyChanged += Obj_PropertyChanged;
}
obj.PropertyRead += Obj_PropertyRead;
}
private void Obj_PropertyRead (object sender, PropertyChangedEventArgs e) => OnPropertyRead (sender, e.PropertyName);
private void Obj_PropertyChanged (object sender, PropertyChangedEventArgs e)
{
if (sender is BindingObject b) {
OnPropertyChanged (sender, e.PropertyName, b.GetValue (e.PropertyName));
return;
}
var value = sender.GetPropertyValue (e.PropertyName);
OnPropertyChanged (sender, e.PropertyName, value);
}
public void StopMonitoring (INotifyPropertyRead obj)
{
if (!children.Contains (obj))
return;
children.Remove (obj);
if (obj is BindingObject b) {
b.OnPropertyChanged = null;
} else {
obj.PropertyChanged -= Obj_PropertyRead;
}
obj.PropertyRead -= Obj_PropertyRead;
}
internal void EndBuildingView ()
{
listProperties.Clear ();
isBuilding = false;
}
internal void StartProperty ()
{
isBuilding = true;
if (listProperties.Any ()) {
BindingState.AddGlobalProperties (listProperties);
}
listProperties.Clear ();
}
internal string [] EndProperty (bool includeParent = false)
{
var changed = listProperties.Distinct ().ToArray ();
listProperties.Clear ();
return changed;
}
bool isUpdating;
public void StartUpdate ()
{
isUpdating = true;
}
public void EndUpdate ()
{
isUpdating = false;
if (pendingUpdates.Any ()) {
if (!BindingState.UpdateValues (pendingUpdates)) {
pendingUpdates.Clear ();
StateChanged?.Invoke ();
return;
}
}
pendingUpdates.Clear ();
}
void OnPropertyChanged (object sender, string propertyName, object value)
{
if (value?.GetType () == typeof (View))
return;
changeDictionary [propertyName] = value;
childrenProperty.TryGetValue (sender, out var parentproperty);
var prop = string.IsNullOrWhiteSpace (parentproperty) ? propertyName : $"{parentproperty}.{propertyName}";
pendingUpdates.Add ((prop, value));
if (!isUpdating) {
EndUpdate ();
}
}
void OnPropertyRead (object sender, string propertyName)
{
if (!isBuilding)
return;
childrenProperty.TryGetValue (sender, out var parentproperty);
var prop = string.IsNullOrWhiteSpace (parentproperty) ? propertyName : $"{parentproperty}.{propertyName}";
listProperties.Add (prop);
}
bool hasChecked;
static Assembly HotUIAssembly = typeof (BindingObject).Assembly;
void CheckForStateAttributes (object obj)
{
if (hasChecked && obj == this)
return;
var type = obj.GetType ();
//var properties = type.GetProperties (BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).
// Where (x => Attribute.IsDefined (x, typeof (StateAttribute))).ToList ();
var fields = type.GetFields (BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).
@ -84,9 +223,10 @@ namespace HotUI {
if (fields.Any ()) {
foreach (var field in fields) {
var child = field.GetValue (this) as BindingObject;
var child = field.GetValue (obj) as INotifyPropertyRead;
if (child != null) {
SetProperty (child, field.Name);
childrenProperty [child] = field.Name;
StartMonitoring (child);
}
}
}
@ -94,148 +234,63 @@ namespace HotUI {
hasChecked = true;
}
internal void EndBuildingView ()
{
}
bindableChildren.ForEach (x => x.EndBuildingView ());
listProperties.Clear ();
isBuilding = false;
public class BindingState {
public List<string> GlobalProperties { get; set; } = new List<string> ();
public Dictionary<string, List<Action<string, object>>> ViewUpdateProperties = new Dictionary<string, List<Action<string, object>>> ();
public void AddGlobalProperty (string property)
{
if (GlobalProperties.Contains (property))
return;
Debug.WriteLine ($"Adding Global Property: {property}");
GlobalProperties.Add (property);
}
public void AddGlobalProperties (IEnumerable<string> properties)
{
foreach (var prop in properties)
AddGlobalProperty (prop);
}
public void AddViewProperty (string property, Action<string, object> update)
{
if (!ViewUpdateProperties.TryGetValue (property, out var actions))
ViewUpdateProperties [property] = actions = new List<Action<string, object>> ();
actions.Add (update);
}
List<string> listProperties = new List<string> ();
internal void StartProperty ()
public void AddViewProperty (string [] properties, Action<string, object> update)
{
isBuilding = true;
if (listProperties.Any ()) {
BindingState.AddGlobalProperties (listProperties);
foreach (var property in properties) {
AddViewProperty (property, update);
}
listProperties.Clear ();
bindableChildren.ForEach (x => x.StartProperty ());
}
internal string [] EndProperty (bool includeParent = false)
public void Clear ()
{
var children = bindableChildren.SelectMany (x => x.EndProperty (true));
var props = listProperties.Select (x => includeParent ? $"{ParentProperty}.{x}" : x).Union (children).Distinct ().ToArray ();
listProperties.Clear ();
return props;
}
//private List<string> CheckChildrenProperties(List<string> properties)
//{
// List<string> realProperties = new List<string> ();
// foreach(var prop in properties) {
// if(bindableChildren.TryGetValue(prop, out var child)) {
// var childProps =
// }
// realProperties.Add (prop);
// }
//}
bool isUpdating;
public void StartUpdate ()
{
isUpdating = true;
}
public void EndUpdate ()
{
isUpdating = false;
if (pendingUpdates.Any ()) {
if (UpdateParentValueChanged != null) {
var updates = pendingUpdates.ToList ();
pendingUpdates.Clear ();
UpdateParentValueChanged (this, updates);
} else if (!BindingState.UpdateValues (pendingUpdates)) {
pendingUpdates.Clear ();
StateChanged?.Invoke ();
return;
}
GlobalProperties?.Clear ();
foreach (var key in ViewUpdateProperties) {
key.Value.Clear ();
}
pendingUpdates.Clear ();
ViewUpdateProperties.Clear ();
}
protected T GetProperty<T> ([CallerMemberName] string propertyName = "")
{
//Ignore View for the ViewBuilders
if (isBuilding && typeof(T) != typeof(View)) {
listProperties.Add (propertyName);
}
if (dictionary.TryGetValue (propertyName, out var val))
return (T)val;
return default (T);
}
/// <summary>
/// Returns true if the value changed
/// This returns true, if it updated the UI based on the changes
/// False, if it couldnt update, or the value was global so the whole UI needs refreshed
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="currentValue"></param>
/// <param name="newValue"></param>
/// <param name="propertyName"></param>
/// <param name="updates"></param>
/// <returns></returns>
protected bool SetProperty<T> (T value, [CallerMemberName] string propertyName = "")
public bool UpdateValues (IEnumerable<(string property, object value)> updates)
{
if (dictionary.TryGetValue (propertyName, out object val)) {
if (EqualityComparer<T>.Default.Equals ((T)val, value))
return true;
if (val is BindingObject oldChild) {
UntrackChild (propertyName, oldChild);
bool didUpdate = true;
foreach (var update in updates) {
if (GlobalProperties.Contains (update.property))
return false;
if (ViewUpdateProperties.TryGetValue (update.property, out var actions)) {
foreach (var a in actions)
a.Invoke (update.property, update.value);
didUpdate = true;
}
}
if (value is BindingObject b) {
TrackChild (propertyName, b);
}
dictionary [propertyName] = value;
//If we track the ViewBuilder.View property it can easily get into a loop!
if (typeof (T) != typeof (View)) {
changeDictionary [propertyName] = value;
//If this is tied to a parent, we need to send that notification as well
if (!string.IsNullOrWhiteSpace (ParentProperty))
pendingUpdates.Add (($"{ParentProperty}.{propertyName}", value));
pendingUpdates.Add ((propertyName, value));
if (!isUpdating) {
EndUpdate ();
}
}
PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (propertyName));
return true;
return didUpdate;
}
List<BindingObject> bindableChildren = new List<BindingObject> ();
void TrackChild (string propertyName, BindingObject child)
{
//This should fail if there are two properties with the same binding object as it's value. But that should never happen!
child.ParentProperty = propertyName;
child.UpdateParentValueChanged = Child_PropertiesChanged;
child.BindingState = this.BindingState;
bindableChildren.Add (child);
}
private void Child_PropertiesChanged (BindingObject child, List<(string property, object value)> changes)
{
pendingUpdates.AddRange (changes);
EndUpdate ();
}
void UntrackChild (string propertyName, BindingObject child)
{
child.UpdateParentValueChanged = null;
child.ParentProperty = null;
child.BindingState = new BindingState ();
bindableChildren.Remove (child);
}
}
}

Просмотреть файл

@ -1,7 +1,18 @@
using System;
namespace HotUI {
public class Button : View {
public Button ()
{
}
public Button (string text)
{
Text = text;
}
public Button (Func<string> formatedText)
{
TextBinding = formatedText;
}
private string text;
public string Text {
get => text;

Просмотреть файл

@ -1,6 +1,18 @@
using System;
namespace HotUI {
public class Entry : View {
public Entry ()
{
}
public Entry (string text)
{
Text = text;
}
public Entry (Func<string> formatedText)
{
TextBinding = formatedText;
}
string text;
public string Text {

Просмотреть файл

@ -1,6 +1,11 @@
using System;
namespace HotUI {
public class Image : View {
public Image () { }
public Image(string source)
{
Source = source;
}
string source;
public string Source {
get => source;

Просмотреть файл

@ -2,7 +2,18 @@
using System.Diagnostics;
namespace HotUI {
public class Label : View {
public Label()
{
}
public Label(string text)
{
Text = text;
}
public Label(Func<string> formatedText)
{
TextBinding = formatedText;
}
private string text;
public string Text {
get => text;

Просмотреть файл

@ -13,17 +13,19 @@ namespace HotUI {
{
ReBuildView ();
}
View view;
public View View {
get => GetProperty<View> ();
get => view;
protected set {
if (SetProperty (value)) {
ViewHandler?.SetView (value);
}
if (view == value)
return;
view = value;
ViewHandler?.SetView (value);
}
}
public void ReBuildView()
public void ReBuildView ()
{
var oldView = View;
BindingState.Clear ();
@ -59,83 +61,4 @@ namespace HotUI {
}
public abstract class StateViewBuilder {
State state;
public State State {
get {
if (state == null) {
state = new State ();
CreateState (state);
//state.StateChanged = Reload;
state.ResetChangeDictionary ();
}
return state;
}
set => state = value;
}
protected abstract void CreateState (dynamic state);
protected abstract View Build (dynamic state);
}
public class BindingState {
public List<string> GlobalProperties { get; set; } = new List<string> ();
public Dictionary<string, List<Action<string,object>>> ViewUpdateProperties = new Dictionary<string, List<Action<string,object>>> ();
public void AddGlobalProperty (string property)
{
Debug.WriteLine ($"Adding Global Property: {property}");
GlobalProperties.Add (property);
}
public void AddGlobalProperties(IEnumerable<string> properties)
{
foreach(var prop in properties)
AddGlobalProperty (prop);
}
public void AddViewProperty(string property, Action<string,object> update)
{
if (!ViewUpdateProperties.TryGetValue (property, out var actions))
ViewUpdateProperties[property] = actions = new List<Action<string,object>> ();
actions.Add (update);
}
public void AddViewProperty (string[] properties, Action<string,object> update)
{
foreach(var property in properties) {
AddViewProperty (property, update);
}
}
public void Clear()
{
GlobalProperties?.Clear ();
foreach(var key in ViewUpdateProperties) {
key.Value.Clear ();
}
ViewUpdateProperties.Clear ();
}
/// <summary>
/// This returns true, if it updated the UI based on the changes
/// False, if it couldnt update, or the value was global so the whole UI needs refreshed
/// </summary>
/// <param name="updates"></param>
/// <returns></returns>
public bool UpdateValues(IEnumerable<(string property,object value)> updates)
{
bool didUpdate = true;
foreach(var update in updates) {
if (GlobalProperties.Contains (update.property))
return false;
if(ViewUpdateProperties.TryGetValue(update.property, out var actions)) {
foreach (var a in actions)
a.Invoke (update.property, update.value);
didUpdate = true;
}
}
return didUpdate;
}
}
}

Просмотреть файл

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace HotUI {
@ -17,7 +18,8 @@ namespace HotUI {
if (propCount == 1) {
var prop = props [0];
var stateValue = state.GetValue (prop).Cast<T>();
var stateValue = state.GetValue (prop).Cast<T> ();
var old = state.EndProperty ();
//1 to 1 binding!
if (EqualityComparer<T>.Default.Equals (stateValue, newValue)) {
state.BindingState.AddViewProperty (prop, onUpdate);
@ -42,17 +44,16 @@ namespace HotUI {
onUpdate (propertyName, newValue);
}
static T Cast<T>(this object val)
static T Cast<T> (this object val)
{
if (val == null)
return default;
try {
if(typeof(T) == typeof(string)) {
if (typeof (T) == typeof (string)) {
return (T)(object)val?.ToString ();
}
return (T)val;
}
catch(Exception ex) {
} catch (Exception ex) {
//This is ok, sometimes the values are not the same.
return default;
}
@ -65,10 +66,10 @@ namespace HotUI {
public static View Diff (this View newView, View oldView)
{
var v = newView.DiffUpdate (oldView);
void callUpdateOnView(View view)
void callUpdateOnView (View view)
{
if(view is IContainerView container) {
foreach(var child in container.GetChildren()) {
if (view is IContainerView container) {
foreach (var child in container.GetChildren ()) {
callUpdateOnView (child);
}
}
@ -134,7 +135,7 @@ namespace HotUI {
}
}
newView.UpdateFromOldView(oldView.ViewHandler);
newView.UpdateFromOldView (oldView.ViewHandler);
return newView;
@ -157,5 +158,35 @@ namespace HotUI {
return view?.GetType () == compareView?.GetType ();
}
public static object GetPropertyValue (this object obj, string name)
{
foreach (var part in name.Split ('.')) {
if (obj == null)
return null;
if (obj is BindingObject bo) {
obj = bo.GetValue (part);
} else {
var type = obj.GetType ();
var info = type.GetProperty (part, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
if (info == null) {
var field = type.GetField (part, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
if (field == null)
return null;
obj = field.GetValue (obj);
} else {
obj = info.GetValue (obj, null);
}
}
}
return obj;
}
public static T GetPropValue<T> (this object obj, string name)
{
var retval = GetPropertyValue (obj, name);
if (retval == null)
return default;
return (T)retval;
}
}
}

Просмотреть файл

@ -46,19 +46,20 @@ namespace HotUI {
[Serializable]
public class State : BindingObject {
public class State : BindingObjectManager {
internal object GetValue (string property)
{
var bindingParts = property.Split ('.');
var dict = dictionary;
for (var i = 0; i < bindingParts.Length - 1; i++) {
var part = bindingParts [i];
dict.TryGetValue (part, out var val);
var child = val as BindingObject;
dict = child.dictionary;
}
dict.TryGetValue (bindingParts.Last (), out var value);
return value;
return this.GetPropertyValue (property);
//var bindingParts = property.Split ('.');
//var dict = dictionary;
//for (var i = 0; i < bindingParts.Length - 1; i++) {
// var part = bindingParts [i];
// dict.TryGetValue (part, out var val);
// var child = val as BindingObject;
// dict = child.dictionary;
//}
//dict.TryGetValue (bindingParts.Last (), out var value);
//return value;
}
}
}